@idealyst/datepicker 1.2.118 → 1.2.121
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 +3 -3
- package/src/DateInput.native.tsx +33 -12
- package/src/DateInput.web.tsx +40 -17
- package/src/DatePicker.native.tsx +34 -5
- package/src/DatePicker.styles.ts +49 -27
- package/src/DatePicker.web.tsx +43 -14
- package/src/DateTimePicker.native.tsx +3 -0
- package/src/DateTimePicker.web.tsx +3 -0
- package/src/TimeInput.native.tsx +33 -12
- package/src/TimeInput.web.tsx +40 -17
- package/src/index.native.ts +1 -0
- package/src/index.ts +1 -0
- package/src/types.ts +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/datepicker",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.121",
|
|
4
4
|
"description": "Cross-platform date and time picker components for React and React Native",
|
|
5
5
|
"documentation": "https://github.com/IdealystIO/idealyst-framework/tree/main/packages/datepicker#readme",
|
|
6
6
|
"readme": "README.md",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"publish:npm": "npm publish"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@idealyst/theme": "^1.2.
|
|
39
|
+
"@idealyst/theme": "^1.2.121",
|
|
40
40
|
"@mdi/js": ">=7.0.0",
|
|
41
41
|
"@mdi/react": ">=1.6.0",
|
|
42
42
|
"react": ">=16.8.0",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
}
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@idealyst/theme": "^1.2.
|
|
72
|
+
"@idealyst/theme": "^1.2.121",
|
|
73
73
|
"@mdi/js": "^7.4.47",
|
|
74
74
|
"@mdi/react": "^1.6.1",
|
|
75
75
|
"@types/react": "^19.1.0",
|
package/src/DateInput.native.tsx
CHANGED
|
@@ -13,6 +13,7 @@ export const DateInput: React.FC<DateInputProps> = ({
|
|
|
13
13
|
minDate,
|
|
14
14
|
maxDate,
|
|
15
15
|
disabled = false,
|
|
16
|
+
pressable = false,
|
|
16
17
|
error,
|
|
17
18
|
size = 'md',
|
|
18
19
|
style,
|
|
@@ -98,23 +99,43 @@ export const DateInput: React.FC<DateInputProps> = ({
|
|
|
98
99
|
{label}
|
|
99
100
|
</Text>
|
|
100
101
|
)}
|
|
101
|
-
|
|
102
|
-
<TextInput
|
|
103
|
-
value={inputValue}
|
|
104
|
-
onChangeText={handleInputChange}
|
|
105
|
-
onBlur={handleInputBlur}
|
|
106
|
-
placeholder={placeholder}
|
|
107
|
-
editable={!disabled}
|
|
108
|
-
style={textInputStyle}
|
|
109
|
-
/>
|
|
102
|
+
{pressable ? (
|
|
110
103
|
<TouchableOpacity
|
|
111
|
-
style={iconButtonStyle}
|
|
112
104
|
onPress={() => !disabled && setOpen(true)}
|
|
113
105
|
disabled={disabled}
|
|
106
|
+
activeOpacity={0.7}
|
|
114
107
|
>
|
|
115
|
-
<
|
|
108
|
+
<View style={inputContainerStyle} pointerEvents="none">
|
|
109
|
+
<TextInput
|
|
110
|
+
value={formatDate(value ?? undefined)}
|
|
111
|
+
placeholder={placeholder}
|
|
112
|
+
editable={false}
|
|
113
|
+
style={textInputStyle}
|
|
114
|
+
/>
|
|
115
|
+
<View style={iconButtonStyle}>
|
|
116
|
+
<MaterialDesignIcons name="calendar" size={iconStyle.width} style={iconStyle} />
|
|
117
|
+
</View>
|
|
118
|
+
</View>
|
|
116
119
|
</TouchableOpacity>
|
|
117
|
-
|
|
120
|
+
) : (
|
|
121
|
+
<View style={inputContainerStyle}>
|
|
122
|
+
<TextInput
|
|
123
|
+
value={inputValue}
|
|
124
|
+
onChangeText={handleInputChange}
|
|
125
|
+
onBlur={handleInputBlur}
|
|
126
|
+
placeholder={placeholder}
|
|
127
|
+
editable={!disabled}
|
|
128
|
+
style={textInputStyle}
|
|
129
|
+
/>
|
|
130
|
+
<TouchableOpacity
|
|
131
|
+
style={iconButtonStyle}
|
|
132
|
+
onPress={() => !disabled && setOpen(true)}
|
|
133
|
+
disabled={disabled}
|
|
134
|
+
>
|
|
135
|
+
<MaterialDesignIcons name="calendar" size={iconStyle.width} style={iconStyle} />
|
|
136
|
+
</TouchableOpacity>
|
|
137
|
+
</View>
|
|
138
|
+
)}
|
|
118
139
|
{error && (
|
|
119
140
|
<Text style={errorTextStyle}>
|
|
120
141
|
{error}
|
package/src/DateInput.web.tsx
CHANGED
|
@@ -18,6 +18,7 @@ export const DateInput: React.FC<DateInputProps> = ({
|
|
|
18
18
|
minDate,
|
|
19
19
|
maxDate,
|
|
20
20
|
disabled = false,
|
|
21
|
+
pressable = false,
|
|
21
22
|
error,
|
|
22
23
|
size = 'md',
|
|
23
24
|
style,
|
|
@@ -113,25 +114,47 @@ export const DateInput: React.FC<DateInputProps> = ({
|
|
|
113
114
|
{label && (
|
|
114
115
|
<span {...labelProps}>{label}</span>
|
|
115
116
|
)}
|
|
116
|
-
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
value={inputValue}
|
|
120
|
-
onChange={handleInputChange}
|
|
121
|
-
onBlur={handleInputBlur}
|
|
122
|
-
placeholder={placeholder}
|
|
123
|
-
disabled={disabled}
|
|
124
|
-
{...inputProps}
|
|
125
|
-
/>
|
|
126
|
-
<button
|
|
127
|
-
type="button"
|
|
128
|
-
{...iconButtonProps}
|
|
117
|
+
{pressable ? (
|
|
118
|
+
<div
|
|
119
|
+
ref={triggerRef}
|
|
129
120
|
onClick={() => !disabled && setOpen(!open)}
|
|
130
|
-
|
|
121
|
+
style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}
|
|
131
122
|
>
|
|
132
|
-
<
|
|
133
|
-
|
|
134
|
-
|
|
123
|
+
<div {...containerProps} style={{ pointerEvents: 'none' }}>
|
|
124
|
+
<input
|
|
125
|
+
type="text"
|
|
126
|
+
value={formatDate(value ?? undefined)}
|
|
127
|
+
placeholder={placeholder}
|
|
128
|
+
readOnly
|
|
129
|
+
tabIndex={-1}
|
|
130
|
+
{...inputProps}
|
|
131
|
+
/>
|
|
132
|
+
<div {...iconButtonProps}>
|
|
133
|
+
<IconSvg path={mdiCalendar} size={iconSize} color={iconColor} />
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
) : (
|
|
138
|
+
<div {...containerProps} ref={triggerRef}>
|
|
139
|
+
<input
|
|
140
|
+
type="text"
|
|
141
|
+
value={inputValue}
|
|
142
|
+
onChange={handleInputChange}
|
|
143
|
+
onBlur={handleInputBlur}
|
|
144
|
+
placeholder={placeholder}
|
|
145
|
+
disabled={disabled}
|
|
146
|
+
{...inputProps}
|
|
147
|
+
/>
|
|
148
|
+
<button
|
|
149
|
+
type="button"
|
|
150
|
+
{...iconButtonProps}
|
|
151
|
+
onClick={() => !disabled && setOpen(!open)}
|
|
152
|
+
disabled={disabled}
|
|
153
|
+
>
|
|
154
|
+
<IconSvg path={mdiCalendar} size={iconSize} color={iconColor} />
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
)}
|
|
135
158
|
{error && (
|
|
136
159
|
<span {...errorProps}>{error}</span>
|
|
137
160
|
)}
|
|
@@ -2,19 +2,27 @@ import React, { useMemo, useState } from 'react';
|
|
|
2
2
|
import { View, Text, TouchableOpacity } from 'react-native';
|
|
3
3
|
import MaterialDesignIcons from '@react-native-vector-icons/material-design-icons';
|
|
4
4
|
import { datePickerCalendarStyles } from './DatePicker.styles';
|
|
5
|
-
import type { DatePickerProps } from './types';
|
|
5
|
+
import type { DatePickerProps, DayIndicator } from './types';
|
|
6
6
|
|
|
7
7
|
const WEEKDAYS = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
|
|
8
8
|
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
9
9
|
|
|
10
10
|
type ViewMode = 'calendar' | 'months' | 'years';
|
|
11
11
|
|
|
12
|
+
function formatDateKey(date: Date): string {
|
|
13
|
+
const y = date.getFullYear();
|
|
14
|
+
const m = String(date.getMonth() + 1).padStart(2, '0');
|
|
15
|
+
const d = String(date.getDate()).padStart(2, '0');
|
|
16
|
+
return `${y}-${m}-${d}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
12
19
|
export const DatePicker: React.FC<DatePickerProps> = ({
|
|
13
20
|
value,
|
|
14
21
|
onChange,
|
|
15
22
|
minDate,
|
|
16
23
|
maxDate,
|
|
17
24
|
disabled = false,
|
|
25
|
+
indicators,
|
|
18
26
|
style,
|
|
19
27
|
}) => {
|
|
20
28
|
const [currentMonth, setCurrentMonth] = useState(() => value || new Date());
|
|
@@ -44,6 +52,8 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
44
52
|
const selectorItemTextStyle = (styles.selectorItemText as any)({});
|
|
45
53
|
const selectorItemTextSelectedStyle = (styles.selectorItemTextSelected as any)({});
|
|
46
54
|
const iconStyle = (styles.iconColor as any)({});
|
|
55
|
+
const indicatorRowStyle = (styles.indicatorRow as any)({});
|
|
56
|
+
const indicatorStyle = (styles.indicator as any)({});
|
|
47
57
|
|
|
48
58
|
const { days, monthShort, year } = useMemo(() => {
|
|
49
59
|
const year = currentMonth.getFullYear();
|
|
@@ -157,6 +167,13 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
157
167
|
setCurrentMonth(new Date(currentMonth.getFullYear() + 10, currentMonth.getMonth(), 1));
|
|
158
168
|
};
|
|
159
169
|
|
|
170
|
+
// Helper to get indicators for a date
|
|
171
|
+
const getIndicators = (date: Date): DayIndicator[] => {
|
|
172
|
+
if (!indicators) return [];
|
|
173
|
+
const key = formatDateKey(date);
|
|
174
|
+
return indicators[key] || [];
|
|
175
|
+
};
|
|
176
|
+
|
|
160
177
|
// Render month selector
|
|
161
178
|
if (viewMode === 'months') {
|
|
162
179
|
return (
|
|
@@ -176,7 +193,7 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
176
193
|
>
|
|
177
194
|
<Text style={titleTextStyle}>{year}</Text>
|
|
178
195
|
</TouchableOpacity>
|
|
179
|
-
<View style={{ width:
|
|
196
|
+
<View style={{ width: 32 }} />
|
|
180
197
|
</View>
|
|
181
198
|
<View style={monthGridStyle}>
|
|
182
199
|
{MONTHS.map((month, index) => {
|
|
@@ -305,20 +322,24 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
305
322
|
const today = isToday(date);
|
|
306
323
|
const dayDisabled = isDisabled(date);
|
|
307
324
|
const disabledDayButtonStyle = (styles.dayButton as any)({ disabled: dayDisabled });
|
|
325
|
+
const dayIndicators = getIndicators(date);
|
|
308
326
|
|
|
309
327
|
return (
|
|
310
328
|
<View
|
|
311
329
|
key={index}
|
|
312
330
|
style={[
|
|
313
331
|
dayCellStyle,
|
|
314
|
-
selected && selectedDayStyle,
|
|
315
332
|
!isCurrentMonth && { opacity: 0.3 },
|
|
316
|
-
today && !selected && todayDayStyle,
|
|
317
333
|
dayDisabled && { opacity: 0.3 },
|
|
318
334
|
]}
|
|
319
335
|
>
|
|
320
336
|
<TouchableOpacity
|
|
321
|
-
style={[
|
|
337
|
+
style={[
|
|
338
|
+
dayButtonStyle,
|
|
339
|
+
dayDisabled && disabledDayButtonStyle,
|
|
340
|
+
selected && selectedDayStyle,
|
|
341
|
+
today && !selected && todayDayStyle,
|
|
342
|
+
]}
|
|
322
343
|
onPress={() => handleDayPress(date)}
|
|
323
344
|
disabled={dayDisabled}
|
|
324
345
|
>
|
|
@@ -331,6 +352,14 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
331
352
|
{date.getDate()}
|
|
332
353
|
</Text>
|
|
333
354
|
</TouchableOpacity>
|
|
355
|
+
<View style={indicatorRowStyle}>
|
|
356
|
+
{dayIndicators.slice(0, 3).map((ind, i) => (
|
|
357
|
+
<View
|
|
358
|
+
key={ind.key ?? i}
|
|
359
|
+
style={[indicatorStyle, { backgroundColor: ind.color }]}
|
|
360
|
+
/>
|
|
361
|
+
))}
|
|
362
|
+
</View>
|
|
334
363
|
</View>
|
|
335
364
|
);
|
|
336
365
|
})}
|
package/src/DatePicker.styles.ts
CHANGED
|
@@ -19,12 +19,12 @@ export type DatePickerDynamicProps = {
|
|
|
19
19
|
* DatePicker calendar styles with theme reactivity.
|
|
20
20
|
*/
|
|
21
21
|
export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme: Theme) => ({
|
|
22
|
-
// Calendar container
|
|
22
|
+
// Calendar container
|
|
23
23
|
calendar: (_props: DatePickerDynamicProps) => ({
|
|
24
|
-
padding:
|
|
24
|
+
padding: 12,
|
|
25
25
|
backgroundColor: theme.colors.surface.primary,
|
|
26
26
|
borderRadius: 6,
|
|
27
|
-
width:
|
|
27
|
+
width: 280,
|
|
28
28
|
variants: {
|
|
29
29
|
disabled: {
|
|
30
30
|
true: { opacity: 0.6 },
|
|
@@ -38,7 +38,7 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
|
|
|
38
38
|
flexDirection: 'row' as const,
|
|
39
39
|
alignItems: 'center' as const,
|
|
40
40
|
justifyContent: 'space-between' as const,
|
|
41
|
-
marginBottom:
|
|
41
|
+
marginBottom: 8,
|
|
42
42
|
paddingHorizontal: 2,
|
|
43
43
|
_web: {
|
|
44
44
|
display: 'flex',
|
|
@@ -57,15 +57,15 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
|
|
|
57
57
|
// Weekday header row
|
|
58
58
|
weekdayRow: (_props: DatePickerDynamicProps) => ({
|
|
59
59
|
flexDirection: 'row' as const,
|
|
60
|
-
marginBottom:
|
|
60
|
+
marginBottom: 4,
|
|
61
61
|
_web: {
|
|
62
62
|
display: 'flex',
|
|
63
63
|
},
|
|
64
64
|
}),
|
|
65
65
|
|
|
66
66
|
weekdayCell: (_props: DatePickerDynamicProps) => ({
|
|
67
|
-
width:
|
|
68
|
-
height:
|
|
67
|
+
width: 36,
|
|
68
|
+
height: 24,
|
|
69
69
|
alignItems: 'center' as const,
|
|
70
70
|
justifyContent: 'center' as const,
|
|
71
71
|
_web: {
|
|
@@ -73,10 +73,12 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
|
|
|
73
73
|
},
|
|
74
74
|
}),
|
|
75
75
|
|
|
76
|
-
// Calendar grid
|
|
76
|
+
// Calendar grid - fixed height so rows flex evenly regardless of 5 or 6 weeks
|
|
77
77
|
calendarGrid: (_props: DatePickerDynamicProps) => ({
|
|
78
78
|
flexDirection: 'row' as const,
|
|
79
79
|
flexWrap: 'wrap' as const,
|
|
80
|
+
height: 252, // 6 rows × 42px
|
|
81
|
+
alignContent: 'space-evenly' as const,
|
|
80
82
|
_web: {
|
|
81
83
|
display: 'flex',
|
|
82
84
|
},
|
|
@@ -104,12 +106,13 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
|
|
|
104
106
|
},
|
|
105
107
|
}),
|
|
106
108
|
|
|
107
|
-
// Individual day cell -
|
|
109
|
+
// Individual day cell - contains button + indicator row
|
|
108
110
|
dayCell: (_props: DatePickerDynamicProps) => ({
|
|
109
|
-
width:
|
|
110
|
-
height:
|
|
111
|
+
width: 36,
|
|
112
|
+
height: 42,
|
|
111
113
|
alignItems: 'center' as const,
|
|
112
|
-
justifyContent: '
|
|
114
|
+
justifyContent: 'flex-start' as const,
|
|
115
|
+
paddingTop: 2,
|
|
113
116
|
_web: {
|
|
114
117
|
display: 'flex',
|
|
115
118
|
},
|
|
@@ -117,8 +120,8 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
|
|
|
117
120
|
|
|
118
121
|
// Navigation button
|
|
119
122
|
navButton: (_props: DatePickerDynamicProps) => ({
|
|
120
|
-
width:
|
|
121
|
-
height:
|
|
123
|
+
width: 32,
|
|
124
|
+
height: 32,
|
|
122
125
|
alignItems: 'center' as const,
|
|
123
126
|
justifyContent: 'center' as const,
|
|
124
127
|
borderRadius: 4,
|
|
@@ -158,18 +161,18 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
|
|
|
158
161
|
}),
|
|
159
162
|
|
|
160
163
|
titleText: (_props: DatePickerDynamicProps) => ({
|
|
161
|
-
fontSize:
|
|
164
|
+
fontSize: 15,
|
|
162
165
|
fontWeight: '600' as const,
|
|
163
166
|
color: theme.colors.text.primary,
|
|
164
167
|
}),
|
|
165
168
|
|
|
166
|
-
// Day button - fills
|
|
169
|
+
// Day button - fills cell for better click handling
|
|
167
170
|
dayButton: (_props: DatePickerDynamicProps) => ({
|
|
168
|
-
width:
|
|
169
|
-
height:
|
|
171
|
+
width: 32,
|
|
172
|
+
height: 32,
|
|
170
173
|
alignItems: 'center' as const,
|
|
171
174
|
justifyContent: 'center' as const,
|
|
172
|
-
borderRadius:
|
|
175
|
+
borderRadius: 16,
|
|
173
176
|
backgroundColor: 'transparent',
|
|
174
177
|
borderWidth: 0,
|
|
175
178
|
_web: {
|
|
@@ -188,7 +191,7 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
|
|
|
188
191
|
}),
|
|
189
192
|
|
|
190
193
|
dayText: (_props: DatePickerDynamicProps) => ({
|
|
191
|
-
fontSize:
|
|
194
|
+
fontSize: 14,
|
|
192
195
|
color: theme.colors.text.primary,
|
|
193
196
|
_web: {
|
|
194
197
|
pointerEvents: 'none',
|
|
@@ -196,7 +199,7 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
|
|
|
196
199
|
}),
|
|
197
200
|
|
|
198
201
|
weekdayText: (_props: DatePickerDynamicProps) => ({
|
|
199
|
-
fontSize:
|
|
202
|
+
fontSize: 12,
|
|
200
203
|
color: theme.colors.text.secondary,
|
|
201
204
|
}),
|
|
202
205
|
|
|
@@ -232,7 +235,7 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
|
|
|
232
235
|
}),
|
|
233
236
|
|
|
234
237
|
selectorItemText: (_props: DatePickerDynamicProps) => ({
|
|
235
|
-
fontSize:
|
|
238
|
+
fontSize: 13,
|
|
236
239
|
color: theme.colors.text.primary,
|
|
237
240
|
_web: {
|
|
238
241
|
pointerEvents: 'none',
|
|
@@ -249,7 +252,7 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
|
|
|
249
252
|
// Selected day styling
|
|
250
253
|
selectedDay: (_props: DatePickerDynamicProps) => ({
|
|
251
254
|
backgroundColor: theme.intents.primary.primary,
|
|
252
|
-
borderRadius:
|
|
255
|
+
borderRadius: 16,
|
|
253
256
|
_web: {
|
|
254
257
|
background: theme.intents.primary.primary,
|
|
255
258
|
},
|
|
@@ -262,15 +265,34 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
|
|
|
262
265
|
},
|
|
263
266
|
}),
|
|
264
267
|
|
|
265
|
-
// Today styling - subtle background
|
|
268
|
+
// Today styling - subtle gray background
|
|
266
269
|
todayDay: (_props: DatePickerDynamicProps) => ({
|
|
267
|
-
|
|
268
|
-
|
|
270
|
+
borderRadius: 16,
|
|
271
|
+
backgroundColor: theme.colors.pallet.gray?.[200] ?? theme.colors.surface.secondary,
|
|
269
272
|
_web: {
|
|
270
|
-
background: theme.
|
|
273
|
+
background: theme.colors.pallet.gray?.[200] ?? theme.colors.surface.secondary,
|
|
271
274
|
},
|
|
272
275
|
}),
|
|
273
276
|
|
|
277
|
+
// Indicator row below the day number
|
|
278
|
+
indicatorRow: (_props: DatePickerDynamicProps) => ({
|
|
279
|
+
flexDirection: 'row' as const,
|
|
280
|
+
alignItems: 'center' as const,
|
|
281
|
+
justifyContent: 'center' as const,
|
|
282
|
+
height: 6,
|
|
283
|
+
gap: 2,
|
|
284
|
+
_web: {
|
|
285
|
+
display: 'flex',
|
|
286
|
+
},
|
|
287
|
+
}),
|
|
288
|
+
|
|
289
|
+
// Individual indicator dot
|
|
290
|
+
indicator: (_props: DatePickerDynamicProps) => ({
|
|
291
|
+
width: 4,
|
|
292
|
+
height: 4,
|
|
293
|
+
borderRadius: 2,
|
|
294
|
+
}),
|
|
295
|
+
|
|
274
296
|
// Icon color helper
|
|
275
297
|
iconColor: (_props: DatePickerDynamicProps) => ({
|
|
276
298
|
color: theme.colors.text.primary,
|
package/src/DatePicker.web.tsx
CHANGED
|
@@ -3,19 +3,27 @@ import { getWebProps } from 'react-native-unistyles/web';
|
|
|
3
3
|
import { mdiChevronLeft, mdiChevronRight } from '@mdi/js';
|
|
4
4
|
import { IconSvg } from './IconSvg.web';
|
|
5
5
|
import { datePickerCalendarStyles } from './DatePicker.styles';
|
|
6
|
-
import type { DatePickerProps } from './types';
|
|
6
|
+
import type { DatePickerProps, DayIndicator } from './types';
|
|
7
7
|
|
|
8
8
|
const WEEKDAYS = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
|
|
9
9
|
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
10
10
|
|
|
11
11
|
type ViewMode = 'calendar' | 'months' | 'years';
|
|
12
12
|
|
|
13
|
+
function formatDateKey(date: Date): string {
|
|
14
|
+
const y = date.getFullYear();
|
|
15
|
+
const m = String(date.getMonth() + 1).padStart(2, '0');
|
|
16
|
+
const d = String(date.getDate()).padStart(2, '0');
|
|
17
|
+
return `${y}-${m}-${d}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
export const DatePicker: React.FC<DatePickerProps> = ({
|
|
14
21
|
value,
|
|
15
22
|
onChange,
|
|
16
23
|
minDate,
|
|
17
24
|
maxDate,
|
|
18
25
|
disabled = false,
|
|
26
|
+
indicators,
|
|
19
27
|
style,
|
|
20
28
|
}) => {
|
|
21
29
|
const [currentMonth, setCurrentMonth] = useState(() => value || new Date());
|
|
@@ -157,6 +165,8 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
157
165
|
const selectorItemTextStyle = (styles.selectorItemText as any)({});
|
|
158
166
|
const selectorItemTextSelectedStyle = (styles.selectorItemTextSelected as any)({});
|
|
159
167
|
const iconColorStyle = (styles.iconColor as any)({});
|
|
168
|
+
const indicatorRowStyle = (styles.indicatorRow as any)({});
|
|
169
|
+
const indicatorStyle = (styles.indicator as any)({});
|
|
160
170
|
|
|
161
171
|
// Get web props for all elements
|
|
162
172
|
const calendarProps = getWebProps([calendarStyle, style as any]);
|
|
@@ -174,6 +184,14 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
174
184
|
const selectorItemSelectedProps = getWebProps([selectorItemStyle, selectorItemSelectedStyle]);
|
|
175
185
|
const selectorItemTextProps = getWebProps([selectorItemTextStyle]);
|
|
176
186
|
const selectorItemTextSelectedProps = getWebProps([selectorItemTextStyle, selectorItemTextSelectedStyle]);
|
|
187
|
+
const indicatorRowProps = getWebProps([indicatorRowStyle]);
|
|
188
|
+
|
|
189
|
+
// Helper to get indicators for a date
|
|
190
|
+
const getIndicators = (date: Date): DayIndicator[] => {
|
|
191
|
+
if (!indicators) return [];
|
|
192
|
+
const key = formatDateKey(date);
|
|
193
|
+
return indicators[key] || [];
|
|
194
|
+
};
|
|
177
195
|
|
|
178
196
|
// Render month selector
|
|
179
197
|
if (viewMode === 'months') {
|
|
@@ -196,7 +214,7 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
196
214
|
>
|
|
197
215
|
<span {...titleTextProps}>{year}</span>
|
|
198
216
|
</button>
|
|
199
|
-
<div style={{ width:
|
|
217
|
+
<div style={{ width: 32 }} />
|
|
200
218
|
</div>
|
|
201
219
|
<div {...monthGridProps}>
|
|
202
220
|
{MONTHS.map((month, index) => {
|
|
@@ -332,6 +350,7 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
332
350
|
const selected = isSelected(date);
|
|
333
351
|
const today = isToday(date);
|
|
334
352
|
const dayDisabled = isDisabled(date);
|
|
353
|
+
const dayIndicators = getIndicators(date);
|
|
335
354
|
|
|
336
355
|
// Get appropriate button props (className and ref only)
|
|
337
356
|
const buttonProps = dayDisabled
|
|
@@ -343,18 +362,28 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
343
362
|
: dayButtonProps;
|
|
344
363
|
|
|
345
364
|
return (
|
|
346
|
-
<
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
365
|
+
<div key={index} {...dayCellProps}>
|
|
366
|
+
<button
|
|
367
|
+
type="button"
|
|
368
|
+
className={buttonProps.className}
|
|
369
|
+
style={{ opacity: (!isCurrentMonthDay || dayDisabled) ? 0.3 : 1 }}
|
|
370
|
+
onClick={() => handleDayPress(date)}
|
|
371
|
+
disabled={dayDisabled}
|
|
372
|
+
>
|
|
373
|
+
<span {...(selected ? selectedDayTextProps : dayTextProps)}>
|
|
374
|
+
{date.getDate()}
|
|
375
|
+
</span>
|
|
376
|
+
</button>
|
|
377
|
+
<div {...indicatorRowProps}>
|
|
378
|
+
{dayIndicators.slice(0, 3).map((ind, i) => (
|
|
379
|
+
<div
|
|
380
|
+
key={ind.key ?? i}
|
|
381
|
+
className={getWebProps([indicatorStyle]).className}
|
|
382
|
+
style={{ backgroundColor: ind.color }}
|
|
383
|
+
/>
|
|
384
|
+
))}
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
358
387
|
);
|
|
359
388
|
})}
|
|
360
389
|
</div>
|
|
@@ -14,6 +14,7 @@ export const DateTimePicker: React.FC<DateTimePickerProps> = ({
|
|
|
14
14
|
timeMode = '12h',
|
|
15
15
|
minuteStep = 1,
|
|
16
16
|
disabled = false,
|
|
17
|
+
pressable = false,
|
|
17
18
|
error,
|
|
18
19
|
size = 'md',
|
|
19
20
|
style,
|
|
@@ -91,6 +92,7 @@ export const DateTimePicker: React.FC<DateTimePickerProps> = ({
|
|
|
91
92
|
minDate={minDate}
|
|
92
93
|
maxDate={maxDate}
|
|
93
94
|
disabled={disabled}
|
|
95
|
+
pressable={pressable}
|
|
94
96
|
error={error}
|
|
95
97
|
size={size}
|
|
96
98
|
/>
|
|
@@ -103,6 +105,7 @@ export const DateTimePicker: React.FC<DateTimePickerProps> = ({
|
|
|
103
105
|
mode={timeMode}
|
|
104
106
|
minuteStep={minuteStep}
|
|
105
107
|
disabled={disabled}
|
|
108
|
+
pressable={pressable}
|
|
106
109
|
size={size}
|
|
107
110
|
/>
|
|
108
111
|
</View>
|
|
@@ -14,6 +14,7 @@ export const DateTimePicker: React.FC<DateTimePickerProps> = ({
|
|
|
14
14
|
timeMode = '12h',
|
|
15
15
|
minuteStep = 1,
|
|
16
16
|
disabled = false,
|
|
17
|
+
pressable = false,
|
|
17
18
|
error,
|
|
18
19
|
size = 'md',
|
|
19
20
|
style,
|
|
@@ -95,6 +96,7 @@ export const DateTimePicker: React.FC<DateTimePickerProps> = ({
|
|
|
95
96
|
minDate={minDate}
|
|
96
97
|
maxDate={maxDate}
|
|
97
98
|
disabled={disabled}
|
|
99
|
+
pressable={pressable}
|
|
98
100
|
error={error}
|
|
99
101
|
size={size}
|
|
100
102
|
/>
|
|
@@ -107,6 +109,7 @@ export const DateTimePicker: React.FC<DateTimePickerProps> = ({
|
|
|
107
109
|
mode={timeMode}
|
|
108
110
|
minuteStep={minuteStep}
|
|
109
111
|
disabled={disabled}
|
|
112
|
+
pressable={pressable}
|
|
110
113
|
size={size}
|
|
111
114
|
/>
|
|
112
115
|
</div>
|
package/src/TimeInput.native.tsx
CHANGED
|
@@ -13,6 +13,7 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
13
13
|
mode = '12h',
|
|
14
14
|
minuteStep = 1,
|
|
15
15
|
disabled = false,
|
|
16
|
+
pressable = false,
|
|
16
17
|
error,
|
|
17
18
|
size = 'md',
|
|
18
19
|
style,
|
|
@@ -118,23 +119,43 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
118
119
|
{label}
|
|
119
120
|
</Text>
|
|
120
121
|
)}
|
|
121
|
-
|
|
122
|
-
<TextInput
|
|
123
|
-
value={inputValue}
|
|
124
|
-
onChangeText={handleInputChange}
|
|
125
|
-
onBlur={handleInputBlur}
|
|
126
|
-
placeholder={placeholder}
|
|
127
|
-
editable={!disabled}
|
|
128
|
-
style={textInputStyle}
|
|
129
|
-
/>
|
|
122
|
+
{pressable ? (
|
|
130
123
|
<TouchableOpacity
|
|
131
|
-
style={iconButtonStyle}
|
|
132
124
|
onPress={() => !disabled && setOpen(true)}
|
|
133
125
|
disabled={disabled}
|
|
126
|
+
activeOpacity={0.7}
|
|
134
127
|
>
|
|
135
|
-
<
|
|
128
|
+
<View style={inputContainerStyle} pointerEvents="none">
|
|
129
|
+
<TextInput
|
|
130
|
+
value={formatTime(value ?? undefined)}
|
|
131
|
+
placeholder={placeholder}
|
|
132
|
+
editable={false}
|
|
133
|
+
style={textInputStyle}
|
|
134
|
+
/>
|
|
135
|
+
<View style={iconButtonStyle}>
|
|
136
|
+
<MaterialDesignIcons name="clock-outline" size={iconStyle.width} style={iconStyle} />
|
|
137
|
+
</View>
|
|
138
|
+
</View>
|
|
136
139
|
</TouchableOpacity>
|
|
137
|
-
|
|
140
|
+
) : (
|
|
141
|
+
<View style={inputContainerStyle}>
|
|
142
|
+
<TextInput
|
|
143
|
+
value={inputValue}
|
|
144
|
+
onChangeText={handleInputChange}
|
|
145
|
+
onBlur={handleInputBlur}
|
|
146
|
+
placeholder={placeholder}
|
|
147
|
+
editable={!disabled}
|
|
148
|
+
style={textInputStyle}
|
|
149
|
+
/>
|
|
150
|
+
<TouchableOpacity
|
|
151
|
+
style={iconButtonStyle}
|
|
152
|
+
onPress={() => !disabled && setOpen(true)}
|
|
153
|
+
disabled={disabled}
|
|
154
|
+
>
|
|
155
|
+
<MaterialDesignIcons name="clock-outline" size={iconStyle.width} style={iconStyle} />
|
|
156
|
+
</TouchableOpacity>
|
|
157
|
+
</View>
|
|
158
|
+
)}
|
|
138
159
|
{error && (
|
|
139
160
|
<Text style={errorTextStyle}>
|
|
140
161
|
{error}
|
package/src/TimeInput.web.tsx
CHANGED
|
@@ -17,6 +17,7 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
17
17
|
mode = '12h',
|
|
18
18
|
minuteStep = 1,
|
|
19
19
|
disabled = false,
|
|
20
|
+
pressable = false,
|
|
20
21
|
error,
|
|
21
22
|
size = 'md',
|
|
22
23
|
style,
|
|
@@ -137,25 +138,47 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
137
138
|
{label && (
|
|
138
139
|
<span {...labelProps}>{label}</span>
|
|
139
140
|
)}
|
|
140
|
-
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
value={inputValue}
|
|
144
|
-
onChange={handleInputChange}
|
|
145
|
-
onBlur={handleInputBlur}
|
|
146
|
-
placeholder={placeholder}
|
|
147
|
-
disabled={disabled}
|
|
148
|
-
{...inputProps}
|
|
149
|
-
/>
|
|
150
|
-
<button
|
|
151
|
-
type="button"
|
|
152
|
-
{...iconButtonProps}
|
|
141
|
+
{pressable ? (
|
|
142
|
+
<div
|
|
143
|
+
ref={triggerRef}
|
|
153
144
|
onClick={() => !disabled && setOpen(!open)}
|
|
154
|
-
|
|
145
|
+
style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}
|
|
155
146
|
>
|
|
156
|
-
<
|
|
157
|
-
|
|
158
|
-
|
|
147
|
+
<div {...containerProps} style={{ pointerEvents: 'none' }}>
|
|
148
|
+
<input
|
|
149
|
+
type="text"
|
|
150
|
+
value={formatTime(value ?? undefined)}
|
|
151
|
+
placeholder={placeholder}
|
|
152
|
+
readOnly
|
|
153
|
+
tabIndex={-1}
|
|
154
|
+
{...inputProps}
|
|
155
|
+
/>
|
|
156
|
+
<div {...iconButtonProps}>
|
|
157
|
+
<IconSvg path={mdiClockOutline} size={iconSize} color={iconColor} />
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
) : (
|
|
162
|
+
<div {...containerProps} ref={triggerRef}>
|
|
163
|
+
<input
|
|
164
|
+
type="text"
|
|
165
|
+
value={inputValue}
|
|
166
|
+
onChange={handleInputChange}
|
|
167
|
+
onBlur={handleInputBlur}
|
|
168
|
+
placeholder={placeholder}
|
|
169
|
+
disabled={disabled}
|
|
170
|
+
{...inputProps}
|
|
171
|
+
/>
|
|
172
|
+
<button
|
|
173
|
+
type="button"
|
|
174
|
+
{...iconButtonProps}
|
|
175
|
+
onClick={() => !disabled && setOpen(!open)}
|
|
176
|
+
disabled={disabled}
|
|
177
|
+
>
|
|
178
|
+
<IconSvg path={mdiClockOutline} size={iconSize} color={iconColor} />
|
|
179
|
+
</button>
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
159
182
|
{error && (
|
|
160
183
|
<span {...errorProps}>{error}</span>
|
|
161
184
|
)}
|
package/src/index.native.ts
CHANGED
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import type { ViewStyle } from 'react-native';
|
|
2
2
|
import type { Size } from '@idealyst/theme';
|
|
3
3
|
|
|
4
|
+
export interface DayIndicator {
|
|
5
|
+
color: string;
|
|
6
|
+
key?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
4
9
|
export interface DatePickerProps {
|
|
5
10
|
value?: Date;
|
|
6
11
|
onChange: (date: Date) => void;
|
|
7
12
|
minDate?: Date;
|
|
8
13
|
maxDate?: Date;
|
|
9
14
|
disabled?: boolean;
|
|
15
|
+
/** Indicators to render below specific dates. Key format: "YYYY-MM-DD" */
|
|
16
|
+
indicators?: Record<string, DayIndicator[]>;
|
|
10
17
|
style?: ViewStyle;
|
|
11
18
|
}
|
|
12
19
|
|
|
@@ -27,6 +34,8 @@ export interface DateInputProps {
|
|
|
27
34
|
minDate?: Date;
|
|
28
35
|
maxDate?: Date;
|
|
29
36
|
disabled?: boolean;
|
|
37
|
+
/** When true, the entire input area is pressable to open the calendar instead of being a text input. */
|
|
38
|
+
pressable?: boolean;
|
|
30
39
|
error?: string;
|
|
31
40
|
size?: Size;
|
|
32
41
|
style?: ViewStyle;
|
|
@@ -40,6 +49,8 @@ export interface TimeInputProps {
|
|
|
40
49
|
mode?: '12h' | '24h';
|
|
41
50
|
minuteStep?: number;
|
|
42
51
|
disabled?: boolean;
|
|
52
|
+
/** When true, the entire input area is pressable to open the time picker instead of being a text input. */
|
|
53
|
+
pressable?: boolean;
|
|
43
54
|
error?: string;
|
|
44
55
|
size?: Size;
|
|
45
56
|
style?: ViewStyle;
|
|
@@ -54,6 +65,8 @@ export interface DateTimePickerProps {
|
|
|
54
65
|
timeMode?: '12h' | '24h';
|
|
55
66
|
minuteStep?: number;
|
|
56
67
|
disabled?: boolean;
|
|
68
|
+
/** When true, both input areas are pressable to open their pickers instead of being text inputs. */
|
|
69
|
+
pressable?: boolean;
|
|
57
70
|
error?: string;
|
|
58
71
|
size?: Size;
|
|
59
72
|
style?: ViewStyle;
|