@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.
- package/package.json +10 -5
- package/src/DateInput/DateInput.native.tsx +80 -0
- package/src/DateInput/DateInput.styles.tsx +118 -0
- package/src/DateInput/DateInput.web.tsx +79 -0
- package/src/DateInput/DateInputBase.tsx +233 -0
- package/src/DateInput/index.native.ts +2 -0
- package/src/DateInput/index.ts +2 -0
- package/src/DateInput/types.ts +60 -0
- package/src/DatePicker/Calendar.native.tsx +180 -78
- package/src/DatePicker/Calendar.styles.tsx +73 -70
- package/src/DatePicker/DatePicker.native.tsx +24 -6
- package/src/DatePicker/DatePicker.styles.tsx +18 -11
- package/src/DatePicker/DatePicker.web.tsx +1 -1
- package/src/DatePicker/index.ts +1 -1
- package/src/DateRangePicker/RangeCalendar.native.tsx +143 -55
- package/src/DateRangePicker/RangeCalendar.styles.tsx +65 -39
- package/src/DateRangePicker/RangeCalendar.web.tsx +169 -60
- package/src/DateRangePicker/types.ts +9 -0
- package/src/DateTimePicker/DateTimePicker.native.tsx +11 -69
- package/src/DateTimePicker/DateTimePicker.tsx +12 -70
- package/src/DateTimePicker/DateTimePickerBase.tsx +204 -0
- package/src/DateTimePicker/TimePicker.native.tsx +9 -196
- package/src/DateTimePicker/TimePicker.styles.tsx +4 -2
- package/src/DateTimePicker/TimePicker.tsx +9 -401
- package/src/DateTimePicker/TimePickerBase.tsx +232 -0
- package/src/DateTimePicker/primitives/ClockFace.native.tsx +195 -0
- package/src/DateTimePicker/primitives/ClockFace.web.tsx +168 -0
- package/src/DateTimePicker/primitives/TimeInput.native.tsx +53 -0
- package/src/DateTimePicker/primitives/TimeInput.web.tsx +66 -0
- package/src/DateTimePicker/primitives/index.native.ts +2 -0
- package/src/DateTimePicker/primitives/index.ts +2 -0
- package/src/DateTimePicker/primitives/index.web.ts +2 -0
- package/src/DateTimePicker/types.ts +0 -4
- package/src/DateTimePicker/utils/dimensions.native.ts +9 -0
- package/src/DateTimePicker/utils/dimensions.ts +9 -0
- package/src/DateTimePicker/utils/dimensions.web.ts +33 -0
- package/src/DateTimeRangePicker/DateTimeRangePicker.native.tsx +10 -199
- package/src/DateTimeRangePicker/DateTimeRangePicker.styles.tsx +3 -0
- package/src/DateTimeRangePicker/DateTimeRangePicker.web.tsx +11 -131
- package/src/DateTimeRangePicker/DateTimeRangePickerBase.tsx +337 -0
- package/src/DateTimeRangePicker/types.ts +0 -2
- package/src/examples/DatePickerExamples.tsx +42 -118
- package/src/index.native.ts +4 -0
- package/src/index.ts +4 -0
- /package/src/DatePicker/{Calendar.tsx → Calendar.web.tsx} +0 -0
|
@@ -1,10 +1,73 @@
|
|
|
1
|
-
import React, { useState, useMemo } from 'react';
|
|
1
|
+
import React, { useState, useMemo, useCallback, memo, useRef } from 'react';
|
|
2
2
|
import { View, Text, Button } from '@idealyst/components';
|
|
3
|
-
import { TouchableOpacity } from 'react-native';
|
|
3
|
+
import { TouchableOpacity, StyleSheet } from 'react-native';
|
|
4
4
|
import { CalendarProps } from './types';
|
|
5
5
|
import { calendarStyles } from './Calendar.styles';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
// Memoized day cell component for better performance
|
|
8
|
+
const DayCell = memo(({
|
|
9
|
+
date,
|
|
10
|
+
isSelected,
|
|
11
|
+
isDisabled,
|
|
12
|
+
onPress,
|
|
13
|
+
dayButtonStyle,
|
|
14
|
+
textColorSelected,
|
|
15
|
+
textColorDefault
|
|
16
|
+
}: {
|
|
17
|
+
date: Date | null;
|
|
18
|
+
isSelected: boolean;
|
|
19
|
+
isDisabled: boolean;
|
|
20
|
+
onPress: (date: Date) => void;
|
|
21
|
+
dayButtonStyle: any;
|
|
22
|
+
textColorSelected: string;
|
|
23
|
+
textColorDefault: string;
|
|
24
|
+
}) => {
|
|
25
|
+
const handlePress = useCallback(() => {
|
|
26
|
+
if (date && !isDisabled) {
|
|
27
|
+
onPress(date);
|
|
28
|
+
}
|
|
29
|
+
}, [date, isDisabled, onPress]);
|
|
30
|
+
|
|
31
|
+
if (!date) {
|
|
32
|
+
return <View style={calendarStyles.dayCell} />;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<View style={calendarStyles.dayCell}>
|
|
37
|
+
<TouchableOpacity
|
|
38
|
+
onPressIn={handlePress}
|
|
39
|
+
disabled={isDisabled}
|
|
40
|
+
style={[
|
|
41
|
+
dayButtonStyle,
|
|
42
|
+
isSelected && styles.selectedButton,
|
|
43
|
+
isDisabled && styles.disabledButton
|
|
44
|
+
]}
|
|
45
|
+
>
|
|
46
|
+
<Text
|
|
47
|
+
style={[
|
|
48
|
+
styles.dayText,
|
|
49
|
+
{ color: isSelected ? textColorSelected : textColorDefault }
|
|
50
|
+
]}
|
|
51
|
+
>
|
|
52
|
+
{date.getDate()}
|
|
53
|
+
</Text>
|
|
54
|
+
</TouchableOpacity>
|
|
55
|
+
</View>
|
|
56
|
+
);
|
|
57
|
+
}, (prevProps, nextProps) => {
|
|
58
|
+
// Custom comparison function to prevent unnecessary re-renders
|
|
59
|
+
// Only re-render if the relevant props actually changed
|
|
60
|
+
return (
|
|
61
|
+
prevProps.date === nextProps.date &&
|
|
62
|
+
prevProps.isSelected === nextProps.isSelected &&
|
|
63
|
+
prevProps.isDisabled === nextProps.isDisabled &&
|
|
64
|
+
prevProps.onPress === nextProps.onPress
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
DayCell.displayName = 'DayCell';
|
|
69
|
+
|
|
70
|
+
export const Calendar: React.FC<CalendarProps> = memo(({
|
|
8
71
|
value,
|
|
9
72
|
onChange,
|
|
10
73
|
minDate,
|
|
@@ -21,74 +84,96 @@ export const Calendar: React.FC<CalendarProps> = ({
|
|
|
21
84
|
|
|
22
85
|
const currentMonth = controlledCurrentMonth || internalCurrentMonth;
|
|
23
86
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
87
|
+
// Store latest callbacks in refs to avoid recreating functions
|
|
88
|
+
const onChangeRef = useRef(onChange);
|
|
89
|
+
const onMonthChangeRef = useRef(onMonthChange);
|
|
90
|
+
onChangeRef.current = onChange;
|
|
91
|
+
onMonthChangeRef.current = onMonthChange;
|
|
92
|
+
|
|
93
|
+
const handleMonthChange = useCallback((newMonth: Date) => {
|
|
94
|
+
if (onMonthChangeRef.current) {
|
|
95
|
+
onMonthChangeRef.current(newMonth);
|
|
27
96
|
} else {
|
|
28
97
|
setInternalCurrentMonth(newMonth);
|
|
29
98
|
}
|
|
30
|
-
};
|
|
99
|
+
}, []);
|
|
31
100
|
|
|
32
|
-
|
|
101
|
+
// Memoize calendar data calculation with selection state
|
|
102
|
+
const { calendarDays, monthName } = useMemo(() => {
|
|
33
103
|
const monthStart = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), 1);
|
|
34
104
|
const monthEnd = new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 0);
|
|
35
105
|
const daysInMonth = monthEnd.getDate();
|
|
36
106
|
const startingDayOfWeek = monthStart.getDay();
|
|
37
107
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
108
|
+
// Pre-calculate all calendar days with their states
|
|
109
|
+
const days: Array<{
|
|
110
|
+
date: Date | null;
|
|
111
|
+
isSelected: boolean;
|
|
112
|
+
isDisabled: boolean;
|
|
113
|
+
key: string;
|
|
114
|
+
}> = [];
|
|
115
|
+
|
|
116
|
+
// Add empty cells for days before month starts
|
|
117
|
+
for (let i = 0; i < startingDayOfWeek; i++) {
|
|
118
|
+
days.push({
|
|
119
|
+
date: null,
|
|
120
|
+
isSelected: false,
|
|
121
|
+
isDisabled: false,
|
|
122
|
+
key: `empty-${i}`
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Add days of the month with pre-calculated states
|
|
127
|
+
for (let day = 1; day <= daysInMonth; day++) {
|
|
128
|
+
const date = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day);
|
|
129
|
+
const dateTime = date.getTime();
|
|
130
|
+
|
|
131
|
+
// Pre-calculate selection state
|
|
132
|
+
const isSelected = value ? (
|
|
133
|
+
date.getDate() === value.getDate() &&
|
|
134
|
+
date.getMonth() === value.getMonth() &&
|
|
135
|
+
date.getFullYear() === value.getFullYear()
|
|
136
|
+
) : false;
|
|
137
|
+
|
|
138
|
+
// Pre-calculate disabled state
|
|
139
|
+
const isDisabled = disabled ||
|
|
140
|
+
(minDate && dateTime < minDate.getTime()) ||
|
|
141
|
+
(maxDate && dateTime > maxDate.getTime());
|
|
142
|
+
|
|
143
|
+
days.push({
|
|
144
|
+
date,
|
|
145
|
+
isSelected,
|
|
146
|
+
isDisabled: !!isDisabled,
|
|
147
|
+
key: `${date.getFullYear()}-${date.getMonth()}-${day}`
|
|
148
|
+
});
|
|
60
149
|
}
|
|
61
|
-
};
|
|
62
150
|
|
|
63
|
-
|
|
151
|
+
const name = currentMonth.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
|
|
152
|
+
|
|
153
|
+
return { calendarDays: days, monthName: name };
|
|
154
|
+
}, [currentMonth, value, disabled, minDate, maxDate]);
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
const handleDateClick = useCallback((date: Date) => {
|
|
158
|
+
onChangeRef.current(date);
|
|
159
|
+
}, []);
|
|
160
|
+
|
|
161
|
+
const goToPreviousMonth = useCallback(() => {
|
|
64
162
|
const newMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1);
|
|
65
163
|
handleMonthChange(newMonth);
|
|
66
|
-
};
|
|
164
|
+
}, [currentMonth, handleMonthChange]);
|
|
67
165
|
|
|
68
|
-
const goToNextMonth = () => {
|
|
166
|
+
const goToNextMonth = useCallback(() => {
|
|
69
167
|
const newMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1);
|
|
70
168
|
handleMonthChange(newMonth);
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const monthName = currentMonth.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
|
|
74
|
-
|
|
75
|
-
// Create calendar grid
|
|
76
|
-
const calendarDays = [];
|
|
77
|
-
|
|
78
|
-
// Add empty cells for days before month starts
|
|
79
|
-
for (let i = 0; i < startingDayOfWeek; i++) {
|
|
80
|
-
calendarDays.push(null);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Add days of the month
|
|
84
|
-
for (let day = 1; day <= daysInMonth; day++) {
|
|
85
|
-
const date = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day);
|
|
86
|
-
calendarDays.push(date);
|
|
87
|
-
}
|
|
169
|
+
}, [currentMonth, handleMonthChange]);
|
|
88
170
|
|
|
89
171
|
// Use Unistyles
|
|
90
172
|
calendarStyles.useVariants({});
|
|
91
173
|
|
|
174
|
+
// Pre-calculate styles to avoid inline objects
|
|
175
|
+
const dayButtonStyle = calendarStyles.dayButton;
|
|
176
|
+
|
|
92
177
|
return (
|
|
93
178
|
<View style={[calendarStyles.container, style]} testID={testID}>
|
|
94
179
|
{/* Header */}
|
|
@@ -116,7 +201,7 @@ export const Calendar: React.FC<CalendarProps> = ({
|
|
|
116
201
|
|
|
117
202
|
{/* Weekday headers */}
|
|
118
203
|
<View style={calendarStyles.weekdayHeader}>
|
|
119
|
-
{
|
|
204
|
+
{weekdays.map((day) => (
|
|
120
205
|
<View key={day} style={calendarStyles.weekdayCell}>
|
|
121
206
|
<Text style={calendarStyles.weekdayText}>
|
|
122
207
|
{day}
|
|
@@ -127,33 +212,50 @@ export const Calendar: React.FC<CalendarProps> = ({
|
|
|
127
212
|
|
|
128
213
|
{/* Calendar grid */}
|
|
129
214
|
<View style={calendarStyles.calendarGrid}>
|
|
130
|
-
{calendarDays.map((
|
|
131
|
-
<
|
|
132
|
-
{
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
]}
|
|
143
|
-
>
|
|
144
|
-
<Text
|
|
145
|
-
style={{
|
|
146
|
-
color: isDateSelected(date) ? 'white' : 'black',
|
|
147
|
-
fontSize: 13,
|
|
148
|
-
}}
|
|
149
|
-
>
|
|
150
|
-
{date.getDate()}
|
|
151
|
-
</Text>
|
|
152
|
-
</TouchableOpacity>
|
|
153
|
-
)}
|
|
154
|
-
</View>
|
|
215
|
+
{calendarDays.map((dayInfo) => (
|
|
216
|
+
<DayCell
|
|
217
|
+
key={dayInfo.key}
|
|
218
|
+
date={dayInfo.date}
|
|
219
|
+
isSelected={dayInfo.isSelected}
|
|
220
|
+
isDisabled={dayInfo.isDisabled}
|
|
221
|
+
onPress={handleDateClick}
|
|
222
|
+
dayButtonStyle={dayButtonStyle}
|
|
223
|
+
textColorSelected="#ffffff"
|
|
224
|
+
textColorDefault="#000000"
|
|
225
|
+
/>
|
|
155
226
|
))}
|
|
156
227
|
</View>
|
|
157
228
|
</View>
|
|
158
229
|
);
|
|
159
|
-
}
|
|
230
|
+
}, (prevProps, nextProps) => {
|
|
231
|
+
// Custom comparison to prevent unnecessary re-renders
|
|
232
|
+
// Skip checking callbacks since we handle them with refs
|
|
233
|
+
return (
|
|
234
|
+
prevProps.value?.getTime() === nextProps.value?.getTime() &&
|
|
235
|
+
prevProps.minDate?.getTime() === nextProps.minDate?.getTime() &&
|
|
236
|
+
prevProps.maxDate?.getTime() === nextProps.maxDate?.getTime() &&
|
|
237
|
+
prevProps.disabled === nextProps.disabled &&
|
|
238
|
+
prevProps.currentMonth?.getTime() === nextProps.currentMonth?.getTime() &&
|
|
239
|
+
prevProps.style === nextProps.style &&
|
|
240
|
+
prevProps.testID === nextProps.testID
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
Calendar.displayName = 'Calendar';
|
|
245
|
+
|
|
246
|
+
// Static weekday labels
|
|
247
|
+
const weekdays = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
|
|
248
|
+
|
|
249
|
+
// Pre-defined styles to avoid inline style objects
|
|
250
|
+
const styles = StyleSheet.create({
|
|
251
|
+
selectedButton: {
|
|
252
|
+
backgroundColor: '#3b82f6'
|
|
253
|
+
},
|
|
254
|
+
disabledButton: {
|
|
255
|
+
opacity: 0.5
|
|
256
|
+
},
|
|
257
|
+
dayText: {
|
|
258
|
+
fontSize: 13,
|
|
259
|
+
textAlign: 'center'
|
|
260
|
+
}
|
|
261
|
+
});
|
|
@@ -38,47 +38,42 @@ export const calendarStyles = StyleSheet.create((theme) => ({
|
|
|
38
38
|
padding: theme.spacing?.sm || 12,
|
|
39
39
|
backgroundColor: theme.colors?.surface?.secondary || '#f9fafb',
|
|
40
40
|
borderRadius: theme.borderRadius?.md || 8,
|
|
41
|
-
|
|
41
|
+
borderWidth: 1,
|
|
42
|
+
borderColor: theme.colors?.border?.primary || '#e5e7eb',
|
|
42
43
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
// Web specific styles
|
|
45
|
+
_web: {
|
|
46
|
+
border: `1px solid ${theme.colors?.border?.primary || '#e5e7eb'}`,
|
|
47
|
+
borderWidth: undefined,
|
|
48
|
+
borderColor: undefined,
|
|
47
49
|
},
|
|
48
50
|
},
|
|
49
51
|
|
|
50
52
|
monthPickerGrid: {
|
|
51
|
-
display: 'flex',
|
|
52
53
|
flexDirection: 'row',
|
|
53
54
|
flexWrap: 'wrap',
|
|
54
|
-
gap: 6,
|
|
55
|
-
width: '100%',
|
|
56
55
|
justifyContent: 'space-between',
|
|
56
|
+
width: '100%',
|
|
57
57
|
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
justifyContent: 'space-between',
|
|
58
|
+
// Web specific styles
|
|
59
|
+
_web: {
|
|
60
|
+
display: 'flex',
|
|
61
|
+
gap: 6,
|
|
63
62
|
},
|
|
64
63
|
},
|
|
65
64
|
|
|
66
65
|
yearPickerGrid: {
|
|
67
|
-
display: 'flex',
|
|
68
66
|
flexDirection: 'row',
|
|
69
67
|
flexWrap: 'wrap',
|
|
70
|
-
|
|
68
|
+
justifyContent: 'space-between',
|
|
71
69
|
maxHeight: 200,
|
|
72
|
-
overflowY: 'auto',
|
|
73
70
|
width: '100%',
|
|
74
|
-
justifyContent: 'space-between',
|
|
75
71
|
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
maxHeight: 200,
|
|
72
|
+
// Web specific styles
|
|
73
|
+
_web: {
|
|
74
|
+
display: 'flex',
|
|
75
|
+
gap: 4,
|
|
76
|
+
overflowY: 'auto',
|
|
82
77
|
},
|
|
83
78
|
},
|
|
84
79
|
|
|
@@ -87,56 +82,64 @@ export const calendarStyles = StyleSheet.create((theme) => ({
|
|
|
87
82
|
paddingHorizontal: theme.spacing?.xs || 6,
|
|
88
83
|
paddingVertical: theme.spacing?.xs || 4,
|
|
89
84
|
minHeight: 32,
|
|
90
|
-
display: 'flex',
|
|
91
85
|
justifyContent: 'center',
|
|
92
86
|
alignItems: 'center',
|
|
87
|
+
width: '30%',
|
|
88
|
+
marginBottom: 4,
|
|
93
89
|
|
|
94
90
|
// Month buttons: 3 per row with equal spacing
|
|
95
91
|
variants: {
|
|
96
92
|
monthButton: {
|
|
97
93
|
true: {
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
_web: {
|
|
95
|
+
flex: '1 0 30%',
|
|
96
|
+
maxWidth: 'calc(33.333% - 4px)',
|
|
97
|
+
width: undefined,
|
|
98
|
+
},
|
|
100
99
|
},
|
|
101
100
|
},
|
|
102
101
|
yearButton: {
|
|
103
102
|
true: {
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
_web: {
|
|
104
|
+
flex: '1 0 22%',
|
|
105
|
+
maxWidth: 'calc(25% - 3px)',
|
|
106
|
+
width: undefined,
|
|
107
|
+
},
|
|
106
108
|
},
|
|
107
109
|
},
|
|
108
110
|
},
|
|
109
111
|
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
// Web specific styles
|
|
113
|
+
_web: {
|
|
114
|
+
display: 'flex',
|
|
115
|
+
width: undefined,
|
|
116
|
+
marginBottom: undefined,
|
|
114
117
|
},
|
|
115
118
|
},
|
|
116
119
|
|
|
117
120
|
weekdayHeader: {
|
|
118
|
-
|
|
119
|
-
gridTemplateColumns: 'repeat(7, 1fr)',
|
|
120
|
-
gap: 2,
|
|
121
|
+
flexDirection: 'row',
|
|
121
122
|
marginBottom: theme.spacing?.xs || 8,
|
|
122
123
|
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
// Web specific styles
|
|
125
|
+
_web: {
|
|
126
|
+
display: 'grid',
|
|
127
|
+
gridTemplateColumns: 'repeat(7, 1fr)',
|
|
128
|
+
gap: 2,
|
|
129
|
+
flexDirection: undefined,
|
|
126
130
|
},
|
|
127
131
|
},
|
|
128
132
|
|
|
129
133
|
weekdayCell: {
|
|
130
|
-
|
|
134
|
+
flex: 1,
|
|
131
135
|
alignItems: 'center',
|
|
132
136
|
justifyContent: 'center',
|
|
133
137
|
paddingVertical: theme.spacing?.xs || 4,
|
|
134
138
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
paddingVertical: theme.spacing?.xs || 4,
|
|
139
|
+
// Web specific styles
|
|
140
|
+
_web: {
|
|
141
|
+
display: 'flex',
|
|
142
|
+
flex: undefined,
|
|
140
143
|
},
|
|
141
144
|
},
|
|
142
145
|
|
|
@@ -147,44 +150,44 @@ export const calendarStyles = StyleSheet.create((theme) => ({
|
|
|
147
150
|
},
|
|
148
151
|
|
|
149
152
|
calendarGrid: {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
gap: 2,
|
|
153
|
+
flexDirection: 'row',
|
|
154
|
+
flexWrap: 'wrap',
|
|
153
155
|
marginBottom: theme.spacing?.xs || 8,
|
|
154
156
|
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
// Web specific styles
|
|
158
|
+
_web: {
|
|
159
|
+
display: 'grid',
|
|
160
|
+
gridTemplateColumns: 'repeat(7, 1fr)',
|
|
161
|
+
gap: 2,
|
|
162
|
+
flexDirection: undefined,
|
|
163
|
+
flexWrap: undefined,
|
|
159
164
|
},
|
|
160
165
|
},
|
|
161
166
|
|
|
162
167
|
dayCell: {
|
|
163
|
-
|
|
168
|
+
width: '14.28%', // 100% / 7 days
|
|
169
|
+
aspectRatio: 1,
|
|
164
170
|
alignItems: 'center',
|
|
165
171
|
justifyContent: 'center',
|
|
166
|
-
aspectRatio: '1',
|
|
167
172
|
minHeight: 32,
|
|
168
173
|
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
alignItems: 'center',
|
|
174
|
-
justifyContent: 'center',
|
|
174
|
+
// Web specific styles
|
|
175
|
+
_web: {
|
|
176
|
+
display: 'flex',
|
|
177
|
+
width: undefined,
|
|
175
178
|
},
|
|
176
179
|
},
|
|
177
180
|
|
|
178
181
|
dayButton: {
|
|
179
|
-
width:
|
|
180
|
-
height:
|
|
181
|
-
maxWidth: 36,
|
|
182
|
-
maxHeight: 36,
|
|
182
|
+
width: 28,
|
|
183
|
+
height: 28,
|
|
183
184
|
minWidth: 28,
|
|
184
185
|
minHeight: 28,
|
|
185
186
|
padding: 0,
|
|
186
187
|
borderRadius: theme.borderRadius?.sm || 6,
|
|
187
188
|
fontSize: theme.typography?.sizes?.small || 13,
|
|
189
|
+
alignItems: 'center',
|
|
190
|
+
justifyContent: 'center',
|
|
188
191
|
|
|
189
192
|
variants: {
|
|
190
193
|
selected: {
|
|
@@ -197,13 +200,13 @@ export const calendarStyles = StyleSheet.create((theme) => ({
|
|
|
197
200
|
},
|
|
198
201
|
},
|
|
199
202
|
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
width:
|
|
203
|
-
height:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
203
|
+
// Web specific styling
|
|
204
|
+
_web: {
|
|
205
|
+
width: '100%',
|
|
206
|
+
height: '100%',
|
|
207
|
+
maxWidth: 36,
|
|
208
|
+
maxHeight: 36,
|
|
209
|
+
display: 'flex',
|
|
207
210
|
},
|
|
208
211
|
},
|
|
209
212
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useCallback, memo, useRef } from 'react';
|
|
2
2
|
import { View, Text } from '@idealyst/components';
|
|
3
3
|
import { DatePickerProps } from './types';
|
|
4
4
|
import { Calendar } from './Calendar.native';
|
|
5
5
|
import { datePickerStyles } from './DatePicker.styles';
|
|
6
6
|
|
|
7
|
-
const DatePicker: React.FC<DatePickerProps> = ({
|
|
7
|
+
const DatePicker: React.FC<DatePickerProps> = memo(({
|
|
8
8
|
value,
|
|
9
9
|
onChange,
|
|
10
10
|
minDate,
|
|
@@ -13,9 +13,14 @@ const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
13
13
|
style,
|
|
14
14
|
testID,
|
|
15
15
|
}) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
// Store the latest onChange function in a ref to avoid recreating handleDateSelect
|
|
17
|
+
const onChangeRef = useRef(onChange);
|
|
18
|
+
onChangeRef.current = onChange;
|
|
19
|
+
|
|
20
|
+
// Stable callback that uses the latest onChange function
|
|
21
|
+
const handleDateSelect = useCallback((date: Date) => {
|
|
22
|
+
onChangeRef.current(date);
|
|
23
|
+
}, []); // Empty dependency array - this function never changes
|
|
19
24
|
|
|
20
25
|
return (
|
|
21
26
|
<Calendar
|
|
@@ -28,6 +33,19 @@ const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
28
33
|
testID={testID}
|
|
29
34
|
/>
|
|
30
35
|
);
|
|
31
|
-
}
|
|
36
|
+
}, (prevProps, nextProps) => {
|
|
37
|
+
// Custom comparison to reduce unnecessary re-renders
|
|
38
|
+
// Skip checking onChange since we handle it with a ref
|
|
39
|
+
return (
|
|
40
|
+
prevProps.value?.getTime() === nextProps.value?.getTime() &&
|
|
41
|
+
prevProps.minDate?.getTime() === nextProps.minDate?.getTime() &&
|
|
42
|
+
prevProps.maxDate?.getTime() === nextProps.maxDate?.getTime() &&
|
|
43
|
+
prevProps.disabled === nextProps.disabled &&
|
|
44
|
+
prevProps.style === nextProps.style &&
|
|
45
|
+
prevProps.testID === nextProps.testID
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
DatePicker.displayName = 'DatePicker';
|
|
32
50
|
|
|
33
51
|
export default DatePicker;
|
|
@@ -14,20 +14,27 @@ export const datePickerStyles = StyleSheet.create((theme) => ({
|
|
|
14
14
|
|
|
15
15
|
picker: {
|
|
16
16
|
borderRadius: theme.borderRadius?.md || 8,
|
|
17
|
-
|
|
17
|
+
borderWidth: 1,
|
|
18
|
+
borderColor: theme.colors?.border?.primary || '#e5e7eb',
|
|
18
19
|
backgroundColor: theme.colors?.surface?.primary || '#ffffff',
|
|
19
20
|
padding: theme.spacing?.md || 16,
|
|
20
|
-
|
|
21
|
+
shadowColor: '#000',
|
|
22
|
+
shadowOffset: { width: 0, height: 1 },
|
|
23
|
+
shadowOpacity: 0.1,
|
|
24
|
+
shadowRadius: 3,
|
|
25
|
+
elevation: 2,
|
|
21
26
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
// Web specific styles
|
|
28
|
+
_web: {
|
|
29
|
+
border: `1px solid ${theme.colors?.border?.primary || '#e5e7eb'}`,
|
|
30
|
+
borderWidth: undefined,
|
|
31
|
+
borderColor: undefined,
|
|
32
|
+
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)',
|
|
33
|
+
shadowColor: undefined,
|
|
34
|
+
shadowOffset: undefined,
|
|
35
|
+
shadowOpacity: undefined,
|
|
36
|
+
shadowRadius: undefined,
|
|
37
|
+
elevation: undefined,
|
|
31
38
|
},
|
|
32
39
|
},
|
|
33
40
|
|
package/src/DatePicker/index.ts
CHANGED