@idealyst/datepicker 1.0.0 → 1.0.58

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 (45) hide show
  1. package/package.json +10 -5
  2. package/src/DateInput/DateInput.native.tsx +80 -0
  3. package/src/DateInput/DateInput.styles.tsx +118 -0
  4. package/src/DateInput/DateInput.web.tsx +79 -0
  5. package/src/DateInput/DateInputBase.tsx +233 -0
  6. package/src/DateInput/index.native.ts +2 -0
  7. package/src/DateInput/index.ts +2 -0
  8. package/src/DateInput/types.ts +60 -0
  9. package/src/DatePicker/Calendar.native.tsx +180 -78
  10. package/src/DatePicker/Calendar.styles.tsx +73 -70
  11. package/src/DatePicker/DatePicker.native.tsx +24 -6
  12. package/src/DatePicker/DatePicker.styles.tsx +18 -11
  13. package/src/DatePicker/DatePicker.web.tsx +1 -1
  14. package/src/DatePicker/index.ts +1 -1
  15. package/src/DateRangePicker/RangeCalendar.native.tsx +143 -55
  16. package/src/DateRangePicker/RangeCalendar.styles.tsx +65 -39
  17. package/src/DateRangePicker/RangeCalendar.web.tsx +169 -60
  18. package/src/DateRangePicker/types.ts +9 -0
  19. package/src/DateTimePicker/DateTimePicker.native.tsx +11 -69
  20. package/src/DateTimePicker/DateTimePicker.tsx +12 -70
  21. package/src/DateTimePicker/DateTimePickerBase.tsx +204 -0
  22. package/src/DateTimePicker/TimePicker.native.tsx +9 -196
  23. package/src/DateTimePicker/TimePicker.styles.tsx +4 -2
  24. package/src/DateTimePicker/TimePicker.tsx +9 -401
  25. package/src/DateTimePicker/TimePickerBase.tsx +232 -0
  26. package/src/DateTimePicker/primitives/ClockFace.native.tsx +195 -0
  27. package/src/DateTimePicker/primitives/ClockFace.web.tsx +168 -0
  28. package/src/DateTimePicker/primitives/TimeInput.native.tsx +53 -0
  29. package/src/DateTimePicker/primitives/TimeInput.web.tsx +66 -0
  30. package/src/DateTimePicker/primitives/index.native.ts +2 -0
  31. package/src/DateTimePicker/primitives/index.ts +2 -0
  32. package/src/DateTimePicker/primitives/index.web.ts +2 -0
  33. package/src/DateTimePicker/types.ts +0 -4
  34. package/src/DateTimePicker/utils/dimensions.native.ts +9 -0
  35. package/src/DateTimePicker/utils/dimensions.ts +9 -0
  36. package/src/DateTimePicker/utils/dimensions.web.ts +33 -0
  37. package/src/DateTimeRangePicker/DateTimeRangePicker.native.tsx +10 -199
  38. package/src/DateTimeRangePicker/DateTimeRangePicker.styles.tsx +3 -0
  39. package/src/DateTimeRangePicker/DateTimeRangePicker.web.tsx +11 -131
  40. package/src/DateTimeRangePicker/DateTimeRangePickerBase.tsx +337 -0
  41. package/src/DateTimeRangePicker/types.ts +0 -2
  42. package/src/examples/DatePickerExamples.tsx +42 -118
  43. package/src/index.native.ts +4 -0
  44. package/src/index.ts +4 -0
  45. /package/src/DatePicker/{Calendar.tsx → Calendar.web.tsx} +0 -0
@@ -0,0 +1,204 @@
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import { View, Button } from '@idealyst/components';
3
+ import { DateTimePickerProps } from './types';
4
+ import { dateTimePickerStyles } from './DateTimePicker.styles';
5
+ import { getDimensions, addEventListener } from './utils/dimensions';
6
+
7
+ interface DateTimePickerBaseProps extends DateTimePickerProps {
8
+ renderCalendar: (props: {
9
+ value: Date | undefined;
10
+ onChange: (date: Date) => void;
11
+ minDate?: Date;
12
+ maxDate?: Date;
13
+ disabled: boolean;
14
+ }) => React.ReactNode;
15
+ renderTimePicker: (props: {
16
+ value: Date;
17
+ onChange: (date: Date) => void;
18
+ disabled: boolean;
19
+ mode: '12h' | '24h';
20
+ step: number;
21
+ }) => React.ReactNode;
22
+ }
23
+
24
+ type ViewMode = 'responsive' | 'date' | 'time';
25
+
26
+ export const DateTimePickerBase: React.FC<DateTimePickerBaseProps> = ({
27
+ value,
28
+ onChange,
29
+ minDate,
30
+ maxDate,
31
+ disabled = false,
32
+ timeMode = '12h',
33
+ timeStep = 1,
34
+ style,
35
+ testID,
36
+ renderCalendar,
37
+ renderTimePicker,
38
+ }) => {
39
+ const [screenData, setScreenData] = useState(() => getDimensions());
40
+ const [viewMode, setViewMode] = useState<ViewMode>('responsive');
41
+
42
+ // Listen for screen dimension changes
43
+ useEffect(() => {
44
+ const subscription = addEventListener(({ window }) => {
45
+ setScreenData(window);
46
+ });
47
+ return () => subscription?.remove();
48
+ }, []);
49
+
50
+ // Determine if we should use side-by-side layout
51
+ const shouldUseSideBySide = screenData.width >= 600; // Tablet width threshold
52
+ const canFitSideBySide = screenData.width >= 480; // Minimum for side-by-side
53
+
54
+ // Auto-adjust view mode based on screen size
55
+ useEffect(() => {
56
+ if (shouldUseSideBySide) {
57
+ setViewMode('responsive');
58
+ } else if (viewMode === 'responsive' && !canFitSideBySide) {
59
+ setViewMode('date');
60
+ }
61
+ }, [shouldUseSideBySide, canFitSideBySide, viewMode]);
62
+
63
+ const handleDateChange = useCallback((newDate: Date) => {
64
+ if (value) {
65
+ // Preserve the time from current value
66
+ const updatedDate = new Date(newDate);
67
+ updatedDate.setHours(value.getHours(), value.getMinutes(), value.getSeconds());
68
+ onChange(updatedDate);
69
+ } else {
70
+ onChange(newDate);
71
+ }
72
+
73
+ // Auto-advance to time selection on small screens after date selection
74
+ if (!canFitSideBySide && viewMode === 'date') {
75
+ setViewMode('time');
76
+ }
77
+ }, [value, onChange, canFitSideBySide, viewMode]);
78
+
79
+ const handleTimeChange = useCallback((newTime: Date) => {
80
+ if (value) {
81
+ // Update time while preserving the date
82
+ const updatedDate = new Date(value);
83
+ updatedDate.setHours(newTime.getHours(), newTime.getMinutes(), newTime.getSeconds());
84
+ onChange(updatedDate);
85
+ } else {
86
+ // If no date is selected, use today's date with the new time
87
+ const today = new Date();
88
+ today.setHours(newTime.getHours(), newTime.getMinutes(), newTime.getSeconds());
89
+ onChange(today);
90
+ }
91
+ }, [value, onChange]);
92
+
93
+ // Side-by-side layout for larger screens
94
+ if (viewMode === 'responsive' && shouldUseSideBySide) {
95
+ return (
96
+ <View style={[dateTimePickerStyles.container, style]} testID={testID} data-testid={testID}>
97
+ {/* Side by side layout */}
98
+ <View style={{
99
+ flexDirection: 'row',
100
+ gap: 16,
101
+ alignItems: 'flex-start',
102
+ _web: {
103
+ display: 'flex',
104
+ flexDirection: 'row',
105
+ gap: 16,
106
+ alignItems: 'flex-start',
107
+ }
108
+ }}>
109
+ {renderCalendar({
110
+ value,
111
+ onChange: handleDateChange,
112
+ minDate,
113
+ maxDate,
114
+ disabled,
115
+ })}
116
+
117
+ {renderTimePicker({
118
+ value: value || new Date(),
119
+ onChange: handleTimeChange,
120
+ disabled,
121
+ mode: timeMode,
122
+ step: timeStep,
123
+ })}
124
+ </View>
125
+ </View>
126
+ );
127
+ }
128
+
129
+ // Step-by-step layout for smaller screens
130
+ const isDateStep = viewMode === 'date';
131
+ const isTimeStep = viewMode === 'time';
132
+
133
+ return (
134
+ <View style={[dateTimePickerStyles.container, style]} testID={testID} data-testid={testID}>
135
+ {/* Step Navigation */}
136
+ <View style={{
137
+ flexDirection: 'row',
138
+ gap: 8,
139
+ marginBottom: 8,
140
+ _web: {
141
+ display: 'flex',
142
+ flexDirection: 'row',
143
+ gap: 8,
144
+ }
145
+ }}>
146
+ <Button
147
+ variant={isDateStep ? 'primary' : 'outlined'}
148
+ size="small"
149
+ onPress={() => setViewMode('date')}
150
+ disabled={disabled}
151
+ style={{ flex: 1 }}
152
+ >
153
+ 1. Date
154
+ </Button>
155
+ <Button
156
+ variant={isTimeStep ? 'primary' : 'outlined'}
157
+ size="small"
158
+ onPress={() => setViewMode('time')}
159
+ disabled={disabled || !value}
160
+ style={{ flex: 1 }}
161
+ >
162
+ 2. Time
163
+ </Button>
164
+ </View>
165
+
166
+ {/* Step Content */}
167
+ {isDateStep && (
168
+ <View>
169
+ {renderCalendar({
170
+ value,
171
+ onChange: handleDateChange,
172
+ minDate,
173
+ maxDate,
174
+ disabled,
175
+ })}
176
+ </View>
177
+ )}
178
+
179
+ {isTimeStep && (
180
+ <View>
181
+ {renderTimePicker({
182
+ value: value || new Date(),
183
+ onChange: handleTimeChange,
184
+ disabled,
185
+ mode: timeMode,
186
+ step: timeStep,
187
+ })}
188
+
189
+ {/* Back to Date button */}
190
+ <View style={{ marginTop: 12, alignItems: 'flex-start' }}>
191
+ <Button
192
+ variant="text"
193
+ size="small"
194
+ onPress={() => setViewMode('date')}
195
+ disabled={disabled}
196
+ >
197
+ ← Back to Date
198
+ </Button>
199
+ </View>
200
+ </View>
201
+ )}
202
+ </View>
203
+ );
204
+ };
@@ -1,204 +1,17 @@
1
1
  import React from 'react';
2
- import { View, Text, Button } from '@idealyst/components';
3
2
  import { TimePickerProps } from './types';
3
+ import { TimePickerBase } from './TimePickerBase';
4
+ import { ClockFace, TimeInput } from './primitives';
4
5
  import { timePickerStyles } from './TimePicker.styles';
5
6
 
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 hours = value.getHours();
17
- const minutes = value.getMinutes();
18
- const seconds = value.getSeconds();
19
-
20
- const displayHours = mode === '12h' ? (hours === 0 ? 12 : hours > 12 ? hours - 12 : hours) : hours;
21
- const ampm = mode === '12h' ? (hours >= 12 ? 'PM' : 'AM') : null;
22
-
23
- const updateTime = (newHours: number, newMinutes: number, newSeconds?: number) => {
24
- const newDate = new Date(value);
25
- newDate.setHours(newHours, newMinutes, newSeconds || 0);
26
- onChange(newDate);
27
- };
28
-
29
- const handleHourChange = (delta: number) => {
30
- let newHours = hours + delta;
31
- if (mode === '12h') {
32
- if (newHours < 0) newHours = 23;
33
- if (newHours > 23) newHours = 0;
34
- } else {
35
- if (newHours < 0) newHours = 23;
36
- if (newHours > 23) newHours = 0;
37
- }
38
- updateTime(newHours, minutes, seconds);
39
- };
40
-
41
- const handleMinuteChange = (delta: number) => {
42
- let newMinutes = minutes + (delta * step);
43
- let newHours = hours;
44
-
45
- if (newMinutes < 0) {
46
- newMinutes = 60 + newMinutes;
47
- newHours = hours - 1;
48
- if (newHours < 0) newHours = 23;
49
- } else if (newMinutes >= 60) {
50
- newMinutes = newMinutes - 60;
51
- newHours = hours + 1;
52
- if (newHours > 23) newHours = 0;
53
- }
54
-
55
- updateTime(newHours, newMinutes, seconds);
56
- };
57
-
58
- const handleSecondChange = (delta: number) => {
59
- let newSeconds = seconds + delta;
60
- let newMinutes = minutes;
61
- let newHours = hours;
62
-
63
- if (newSeconds < 0) {
64
- newSeconds = 59;
65
- newMinutes = minutes - 1;
66
- if (newMinutes < 0) {
67
- newMinutes = 59;
68
- newHours = hours - 1;
69
- if (newHours < 0) newHours = 23;
70
- }
71
- } else if (newSeconds >= 60) {
72
- newSeconds = 0;
73
- newMinutes = minutes + 1;
74
- if (newMinutes >= 60) {
75
- newMinutes = 0;
76
- newHours = hours + 1;
77
- if (newHours > 23) newHours = 0;
78
- }
79
- }
80
-
81
- updateTime(newHours, newMinutes, newSeconds);
82
- };
83
-
84
- const toggleAmPm = () => {
85
- if (mode === '12h') {
86
- const newHours = hours >= 12 ? hours - 12 : hours + 12;
87
- updateTime(newHours, minutes, seconds);
88
- }
89
- };
90
-
7
+ export const TimePicker: React.FC<TimePickerProps> = (props) => {
91
8
  timePickerStyles.useVariants({});
92
-
9
+
93
10
  return (
94
- <View style={[timePickerStyles.container, style]} testID={testID}>
95
- {/* Hours */}
96
- <View style={timePickerStyles.timeSection}>
97
- <View style={timePickerStyles.stepperContainer}>
98
- <Button
99
- variant="text"
100
- size="small"
101
- onPress={() => handleHourChange(1)}
102
- disabled={disabled}
103
- style={timePickerStyles.stepperButton}
104
- >
105
- <Text style={timePickerStyles.stepperText}>▲</Text>
106
- </Button>
107
- <View style={timePickerStyles.timeInput}>
108
- <Text style={{ textAlign: 'center', fontSize: 16, fontWeight: '600' }}>
109
- {String(displayHours).padStart(2, '0')}
110
- </Text>
111
- </View>
112
- <Button
113
- variant="text"
114
- size="small"
115
- onPress={() => handleHourChange(-1)}
116
- disabled={disabled}
117
- style={timePickerStyles.stepperButton}
118
- >
119
- <Text style={timePickerStyles.stepperText}>▼</Text>
120
- </Button>
121
- </View>
122
- </View>
123
-
124
- {/* Separator */}
125
- <Text style={timePickerStyles.timeSeparator}>:</Text>
126
-
127
- {/* Minutes */}
128
- <View style={timePickerStyles.timeSection}>
129
- <View style={timePickerStyles.stepperContainer}>
130
- <Button
131
- variant="text"
132
- size="small"
133
- onPress={() => handleMinuteChange(1)}
134
- disabled={disabled}
135
- style={timePickerStyles.stepperButton}
136
- >
137
- <Text style={timePickerStyles.stepperText}>▲</Text>
138
- </Button>
139
- <View style={timePickerStyles.timeInput}>
140
- <Text style={{ textAlign: 'center', fontSize: 16, fontWeight: '600' }}>
141
- {String(minutes).padStart(2, '0')}
142
- </Text>
143
- </View>
144
- <Button
145
- variant="text"
146
- size="small"
147
- onPress={() => handleMinuteChange(-1)}
148
- disabled={disabled}
149
- style={timePickerStyles.stepperButton}
150
- >
151
- <Text style={timePickerStyles.stepperText}>▼</Text>
152
- </Button>
153
- </View>
154
- </View>
155
-
156
- {/* Seconds */}
157
- {showSeconds && (
158
- <>
159
- <Text style={timePickerStyles.timeSeparator}>:</Text>
160
- <View style={timePickerStyles.timeSection}>
161
- <View style={timePickerStyles.stepperContainer}>
162
- <Button
163
- variant="text"
164
- size="small"
165
- onPress={() => handleSecondChange(1)}
166
- disabled={disabled}
167
- style={timePickerStyles.stepperButton}
168
- >
169
- <Text style={timePickerStyles.stepperText}>▲</Text>
170
- </Button>
171
- <View style={timePickerStyles.timeInput}>
172
- <Text style={{ textAlign: 'center', fontSize: 16, fontWeight: '600' }}>
173
- {String(seconds).padStart(2, '0')}
174
- </Text>
175
- </View>
176
- <Button
177
- variant="text"
178
- size="small"
179
- onPress={() => handleSecondChange(-1)}
180
- disabled={disabled}
181
- style={timePickerStyles.stepperButton}
182
- >
183
- <Text style={timePickerStyles.stepperText}>▼</Text>
184
- </Button>
185
- </View>
186
- </View>
187
- </>
188
- )}
189
-
190
- {/* AM/PM */}
191
- {mode === '12h' && ampm && (
192
- <Button
193
- variant="outlined"
194
- size="small"
195
- onPress={toggleAmPm}
196
- disabled={disabled}
197
- style={timePickerStyles.ampmButton}
198
- >
199
- {ampm}
200
- </Button>
201
- )}
202
- </View>
11
+ <TimePickerBase
12
+ {...props}
13
+ renderClock={(clockProps) => <ClockFace {...clockProps} />}
14
+ renderTimeInput={(inputProps) => <TimeInput {...inputProps} />}
15
+ />
203
16
  );
204
17
  };
@@ -36,8 +36,8 @@ export const timePickerStyles = StyleSheet.create((theme) => ({
36
36
  }
37
37
  },
38
38
  timeInput: {
39
- width: 32,
40
- height: 32,
39
+ width: 44,
40
+ height: 40,
41
41
  textAlign: 'center',
42
42
  fontSize: 16,
43
43
  fontWeight: '600',
@@ -46,6 +46,8 @@ export const timePickerStyles = StyleSheet.create((theme) => ({
46
46
  borderBottomWidth: 0,
47
47
  borderRadius: 0,
48
48
  backgroundColor: 'transparent',
49
+ paddingHorizontal: 0,
50
+ paddingVertical: 0,
49
51
 
50
52
  _web: {
51
53
  border: 'none',