@idealyst/datepicker 1.2.119 → 11.2.120
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 +26 -11
- package/src/DateInput.web.tsx +33 -12
- package/src/DatePicker.native.tsx +38 -11
- package/src/DatePicker.styles.ts +6 -3
- package/src/DatePicker.web.tsx +47 -20
- package/src/DateTimePicker.native.tsx +3 -0
- package/src/DateTimePicker.web.tsx +3 -0
- package/src/InputStyles.ts +29 -0
- package/src/TimeInput.native.tsx +26 -11
- package/src/TimeInput.web.tsx +33 -12
- package/src/index.native.ts +1 -0
- package/src/index.ts +1 -0
- package/src/types.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/datepicker",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "11.2.120",
|
|
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": "^
|
|
39
|
+
"@idealyst/theme": "^11.2.120",
|
|
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": "^
|
|
72
|
+
"@idealyst/theme": "^11.2.120",
|
|
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,37 @@ 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={
|
|
104
|
+
style={inputContainerStyle}
|
|
112
105
|
onPress={() => !disabled && setOpen(true)}
|
|
113
106
|
disabled={disabled}
|
|
107
|
+
activeOpacity={0.7}
|
|
114
108
|
>
|
|
109
|
+
<Text style={[textInputStyle, !value && { color: (styles.pressableText as any)({ disabled, size }).placeholderColor }]}>
|
|
110
|
+
{value ? formatDate(value) : placeholder}
|
|
111
|
+
</Text>
|
|
115
112
|
<MaterialDesignIcons name="calendar" size={iconStyle.width} style={iconStyle} />
|
|
116
113
|
</TouchableOpacity>
|
|
117
|
-
|
|
114
|
+
) : (
|
|
115
|
+
<View style={inputContainerStyle}>
|
|
116
|
+
<TextInput
|
|
117
|
+
value={inputValue}
|
|
118
|
+
onChangeText={handleInputChange}
|
|
119
|
+
onBlur={handleInputBlur}
|
|
120
|
+
placeholder={placeholder}
|
|
121
|
+
editable={!disabled}
|
|
122
|
+
style={textInputStyle}
|
|
123
|
+
/>
|
|
124
|
+
<TouchableOpacity
|
|
125
|
+
style={iconButtonStyle}
|
|
126
|
+
onPress={() => !disabled && setOpen(true)}
|
|
127
|
+
disabled={disabled}
|
|
128
|
+
>
|
|
129
|
+
<MaterialDesignIcons name="calendar" size={iconStyle.width} style={iconStyle} />
|
|
130
|
+
</TouchableOpacity>
|
|
131
|
+
</View>
|
|
132
|
+
)}
|
|
118
133
|
{error && (
|
|
119
134
|
<Text style={errorTextStyle}>
|
|
120
135
|
{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,
|
|
@@ -108,30 +109,50 @@ export const DateInput: React.FC<DateInputProps> = ({
|
|
|
108
109
|
const errorProps = getWebProps([errorTextStyle]);
|
|
109
110
|
const popoverProps = getWebProps([popoverContentStyle]);
|
|
110
111
|
|
|
112
|
+
// Pressable text style (reuses input style but renders as a button)
|
|
113
|
+
const pressableTextStyle = (styles.pressableText as any)({ disabled, size });
|
|
114
|
+
const pressableTextProps = getWebProps([pressableTextStyle]);
|
|
115
|
+
|
|
111
116
|
return (
|
|
112
117
|
<div style={flattenStyle(style)}>
|
|
113
118
|
{label && (
|
|
114
119
|
<span {...labelProps}>{label}</span>
|
|
115
120
|
)}
|
|
116
|
-
|
|
117
|
-
<input
|
|
118
|
-
type="text"
|
|
119
|
-
value={inputValue}
|
|
120
|
-
onChange={handleInputChange}
|
|
121
|
-
onBlur={handleInputBlur}
|
|
122
|
-
placeholder={placeholder}
|
|
123
|
-
disabled={disabled}
|
|
124
|
-
{...inputProps}
|
|
125
|
-
/>
|
|
121
|
+
{pressable ? (
|
|
126
122
|
<button
|
|
127
123
|
type="button"
|
|
128
|
-
{...
|
|
124
|
+
{...containerProps}
|
|
125
|
+
ref={triggerRef as any}
|
|
126
|
+
style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}
|
|
129
127
|
onClick={() => !disabled && setOpen(!open)}
|
|
130
128
|
disabled={disabled}
|
|
131
129
|
>
|
|
130
|
+
<span {...pressableTextProps} style={!value ? { color: pressableTextStyle.placeholderColor } : undefined}>
|
|
131
|
+
{value ? formatDate(value) : placeholder}
|
|
132
|
+
</span>
|
|
132
133
|
<IconSvg path={mdiCalendar} size={iconSize} color={iconColor} />
|
|
133
134
|
</button>
|
|
134
|
-
|
|
135
|
+
) : (
|
|
136
|
+
<div {...containerProps} ref={triggerRef}>
|
|
137
|
+
<input
|
|
138
|
+
type="text"
|
|
139
|
+
value={inputValue}
|
|
140
|
+
onChange={handleInputChange}
|
|
141
|
+
onBlur={handleInputBlur}
|
|
142
|
+
placeholder={placeholder}
|
|
143
|
+
disabled={disabled}
|
|
144
|
+
{...inputProps}
|
|
145
|
+
/>
|
|
146
|
+
<button
|
|
147
|
+
type="button"
|
|
148
|
+
{...iconButtonProps}
|
|
149
|
+
onClick={() => !disabled && setOpen(!open)}
|
|
150
|
+
disabled={disabled}
|
|
151
|
+
>
|
|
152
|
+
<IconSvg path={mdiCalendar} size={iconSize} color={iconColor} />
|
|
153
|
+
</button>
|
|
154
|
+
</div>
|
|
155
|
+
)}
|
|
135
156
|
{error && (
|
|
136
157
|
<span {...errorProps}>{error}</span>
|
|
137
158
|
)}
|
|
@@ -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();
|
|
@@ -71,13 +81,11 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
71
81
|
days.push({ date: new Date(year, month, day, 12, 0, 0, 0), isCurrentMonth: true });
|
|
72
82
|
}
|
|
73
83
|
|
|
74
|
-
// Next month padding (fill to
|
|
84
|
+
// Next month padding (always fill to 42 days = 6 rows for consistent height)
|
|
75
85
|
// Use noon (12:00) to avoid timezone issues when date crosses day boundaries
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
days.push({ date: new Date(year, month + 1, day, 12, 0, 0, 0), isCurrentMonth: false });
|
|
80
|
-
}
|
|
86
|
+
const totalCells = 42;
|
|
87
|
+
for (let day = 1; days.length < totalCells; day++) {
|
|
88
|
+
days.push({ date: new Date(year, month + 1, day, 12, 0, 0, 0), isCurrentMonth: false });
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
return { days, monthShort: MONTHS[month], year };
|
|
@@ -157,6 +165,13 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
157
165
|
setCurrentMonth(new Date(currentMonth.getFullYear() + 10, currentMonth.getMonth(), 1));
|
|
158
166
|
};
|
|
159
167
|
|
|
168
|
+
// Helper to get indicators for a date
|
|
169
|
+
const getIndicators = (date: Date): DayIndicator[] => {
|
|
170
|
+
if (!indicators) return [];
|
|
171
|
+
const key = formatDateKey(date);
|
|
172
|
+
return indicators[key] || [];
|
|
173
|
+
};
|
|
174
|
+
|
|
160
175
|
// Render month selector
|
|
161
176
|
if (viewMode === 'months') {
|
|
162
177
|
return (
|
|
@@ -176,7 +191,7 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
176
191
|
>
|
|
177
192
|
<Text style={titleTextStyle}>{year}</Text>
|
|
178
193
|
</TouchableOpacity>
|
|
179
|
-
<View style={{ width:
|
|
194
|
+
<View style={{ width: 32 }} />
|
|
180
195
|
</View>
|
|
181
196
|
<View style={monthGridStyle}>
|
|
182
197
|
{MONTHS.map((month, index) => {
|
|
@@ -305,20 +320,24 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
305
320
|
const today = isToday(date);
|
|
306
321
|
const dayDisabled = isDisabled(date);
|
|
307
322
|
const disabledDayButtonStyle = (styles.dayButton as any)({ disabled: dayDisabled });
|
|
323
|
+
const dayIndicators = getIndicators(date);
|
|
308
324
|
|
|
309
325
|
return (
|
|
310
326
|
<View
|
|
311
327
|
key={index}
|
|
312
328
|
style={[
|
|
313
329
|
dayCellStyle,
|
|
314
|
-
selected && selectedDayStyle,
|
|
315
330
|
!isCurrentMonth && { opacity: 0.3 },
|
|
316
|
-
today && !selected && todayDayStyle,
|
|
317
331
|
dayDisabled && { opacity: 0.3 },
|
|
318
332
|
]}
|
|
319
333
|
>
|
|
320
334
|
<TouchableOpacity
|
|
321
|
-
style={[
|
|
335
|
+
style={[
|
|
336
|
+
dayButtonStyle,
|
|
337
|
+
dayDisabled && disabledDayButtonStyle,
|
|
338
|
+
selected && selectedDayStyle,
|
|
339
|
+
today && !selected && todayDayStyle,
|
|
340
|
+
]}
|
|
322
341
|
onPress={() => handleDayPress(date)}
|
|
323
342
|
disabled={dayDisabled}
|
|
324
343
|
>
|
|
@@ -331,6 +350,14 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
331
350
|
{date.getDate()}
|
|
332
351
|
</Text>
|
|
333
352
|
</TouchableOpacity>
|
|
353
|
+
<View style={indicatorRowStyle}>
|
|
354
|
+
{dayIndicators.slice(0, 3).map((ind, i) => (
|
|
355
|
+
<View
|
|
356
|
+
key={ind.key ?? i}
|
|
357
|
+
style={[indicatorStyle, { backgroundColor: ind.color }]}
|
|
358
|
+
/>
|
|
359
|
+
))}
|
|
360
|
+
</View>
|
|
334
361
|
</View>
|
|
335
362
|
);
|
|
336
363
|
})}
|
package/src/DatePicker.styles.ts
CHANGED
|
@@ -263,12 +263,15 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
|
|
|
263
263
|
},
|
|
264
264
|
}),
|
|
265
265
|
|
|
266
|
-
// Today styling -
|
|
266
|
+
// Today styling - outlined circle (no fill)
|
|
267
267
|
todayDay: (_props: DatePickerDynamicProps) => ({
|
|
268
|
-
|
|
268
|
+
borderWidth: 1,
|
|
269
|
+
borderColor: theme.intents.primary.primary,
|
|
269
270
|
borderRadius: 16,
|
|
271
|
+
backgroundColor: 'transparent',
|
|
270
272
|
_web: {
|
|
271
|
-
background:
|
|
273
|
+
background: 'transparent',
|
|
274
|
+
boxSizing: 'border-box',
|
|
272
275
|
},
|
|
273
276
|
}),
|
|
274
277
|
|
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());
|
|
@@ -54,13 +62,11 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
54
62
|
days.push({ date: new Date(year, month, day, 12, 0, 0, 0), isCurrentMonth: true });
|
|
55
63
|
}
|
|
56
64
|
|
|
57
|
-
// Next month padding (fill to
|
|
65
|
+
// Next month padding (always fill to 42 days = 6 rows for consistent height)
|
|
58
66
|
// Use noon (12:00) to avoid timezone issues when date crosses day boundaries
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
days.push({ date: new Date(year, month + 1, day, 12, 0, 0, 0), isCurrentMonth: false });
|
|
63
|
-
}
|
|
67
|
+
const totalCells = 42;
|
|
68
|
+
for (let day = 1; days.length < totalCells; day++) {
|
|
69
|
+
days.push({ date: new Date(year, month + 1, day, 12, 0, 0, 0), isCurrentMonth: false });
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
return { days, monthShort: MONTHS[month], year };
|
|
@@ -157,6 +163,8 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
157
163
|
const selectorItemTextStyle = (styles.selectorItemText as any)({});
|
|
158
164
|
const selectorItemTextSelectedStyle = (styles.selectorItemTextSelected as any)({});
|
|
159
165
|
const iconColorStyle = (styles.iconColor as any)({});
|
|
166
|
+
const indicatorRowStyle = (styles.indicatorRow as any)({});
|
|
167
|
+
const indicatorStyle = (styles.indicator as any)({});
|
|
160
168
|
|
|
161
169
|
// Get web props for all elements
|
|
162
170
|
const calendarProps = getWebProps([calendarStyle, style as any]);
|
|
@@ -174,6 +182,14 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
174
182
|
const selectorItemSelectedProps = getWebProps([selectorItemStyle, selectorItemSelectedStyle]);
|
|
175
183
|
const selectorItemTextProps = getWebProps([selectorItemTextStyle]);
|
|
176
184
|
const selectorItemTextSelectedProps = getWebProps([selectorItemTextStyle, selectorItemTextSelectedStyle]);
|
|
185
|
+
const indicatorRowProps = getWebProps([indicatorRowStyle]);
|
|
186
|
+
|
|
187
|
+
// Helper to get indicators for a date
|
|
188
|
+
const getIndicators = (date: Date): DayIndicator[] => {
|
|
189
|
+
if (!indicators) return [];
|
|
190
|
+
const key = formatDateKey(date);
|
|
191
|
+
return indicators[key] || [];
|
|
192
|
+
};
|
|
177
193
|
|
|
178
194
|
// Render month selector
|
|
179
195
|
if (viewMode === 'months') {
|
|
@@ -196,7 +212,7 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
196
212
|
>
|
|
197
213
|
<span {...titleTextProps}>{year}</span>
|
|
198
214
|
</button>
|
|
199
|
-
<div style={{ width:
|
|
215
|
+
<div style={{ width: 32 }} />
|
|
200
216
|
</div>
|
|
201
217
|
<div {...monthGridProps}>
|
|
202
218
|
{MONTHS.map((month, index) => {
|
|
@@ -332,6 +348,7 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
332
348
|
const selected = isSelected(date);
|
|
333
349
|
const today = isToday(date);
|
|
334
350
|
const dayDisabled = isDisabled(date);
|
|
351
|
+
const dayIndicators = getIndicators(date);
|
|
335
352
|
|
|
336
353
|
// Get appropriate button props (className and ref only)
|
|
337
354
|
const buttonProps = dayDisabled
|
|
@@ -343,18 +360,28 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
343
360
|
: dayButtonProps;
|
|
344
361
|
|
|
345
362
|
return (
|
|
346
|
-
<
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
363
|
+
<div key={index} {...dayCellProps}>
|
|
364
|
+
<button
|
|
365
|
+
type="button"
|
|
366
|
+
className={buttonProps.className}
|
|
367
|
+
style={{ opacity: (!isCurrentMonthDay || dayDisabled) ? 0.3 : 1 }}
|
|
368
|
+
onClick={() => handleDayPress(date)}
|
|
369
|
+
disabled={dayDisabled}
|
|
370
|
+
>
|
|
371
|
+
<span {...(selected ? selectedDayTextProps : dayTextProps)}>
|
|
372
|
+
{date.getDate()}
|
|
373
|
+
</span>
|
|
374
|
+
</button>
|
|
375
|
+
<div {...indicatorRowProps}>
|
|
376
|
+
{dayIndicators.slice(0, 3).map((ind, i) => (
|
|
377
|
+
<div
|
|
378
|
+
key={ind.key ?? i}
|
|
379
|
+
className={getWebProps([indicatorStyle]).className}
|
|
380
|
+
style={{ backgroundColor: ind.color }}
|
|
381
|
+
/>
|
|
382
|
+
))}
|
|
383
|
+
</div>
|
|
384
|
+
</div>
|
|
358
385
|
);
|
|
359
386
|
})}
|
|
360
387
|
</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/InputStyles.ts
CHANGED
|
@@ -195,6 +195,35 @@ export const dateTimeInputStyles = defineStyle('DateTimeInput', (theme: Theme) =
|
|
|
195
195
|
color: theme.intents.primary.primary,
|
|
196
196
|
}),
|
|
197
197
|
|
|
198
|
+
// Pressable text (used when pressable=true instead of a text input)
|
|
199
|
+
pressableText: (_props: InputDynamicProps) => ({
|
|
200
|
+
flex: 1,
|
|
201
|
+
minWidth: 0,
|
|
202
|
+
color: theme.colors.text.primary,
|
|
203
|
+
fontWeight: '400' as const,
|
|
204
|
+
textAlign: 'left' as const,
|
|
205
|
+
// Default font size for when size variant isn't specified
|
|
206
|
+
fontSize: theme.sizes.input.md.fontSize,
|
|
207
|
+
// Placeholder color for native when no value is set
|
|
208
|
+
placeholderColor: theme.colors.text.tertiary,
|
|
209
|
+
_web: {
|
|
210
|
+
overflow: 'hidden',
|
|
211
|
+
textOverflow: 'ellipsis',
|
|
212
|
+
whiteSpace: 'nowrap',
|
|
213
|
+
pointerEvents: 'none',
|
|
214
|
+
},
|
|
215
|
+
variants: {
|
|
216
|
+
disabled: {
|
|
217
|
+
true: { color: theme.colors.text.tertiary },
|
|
218
|
+
false: { color: theme.colors.text.primary },
|
|
219
|
+
},
|
|
220
|
+
// $iterator expands for each input size
|
|
221
|
+
size: {
|
|
222
|
+
fontSize: theme.sizes.$input.fontSize,
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
}),
|
|
226
|
+
|
|
198
227
|
// Icon color helper
|
|
199
228
|
iconColor: (_props: InputDynamicProps) => ({
|
|
200
229
|
color: theme.colors.text.secondary,
|
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,37 @@ 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={
|
|
124
|
+
style={inputContainerStyle}
|
|
132
125
|
onPress={() => !disabled && setOpen(true)}
|
|
133
126
|
disabled={disabled}
|
|
127
|
+
activeOpacity={0.7}
|
|
134
128
|
>
|
|
129
|
+
<Text style={[textInputStyle, !value && { color: (styles.pressableText as any)({ disabled, size }).placeholderColor }]}>
|
|
130
|
+
{value ? formatTime(value) : placeholder}
|
|
131
|
+
</Text>
|
|
135
132
|
<MaterialDesignIcons name="clock-outline" size={iconStyle.width} style={iconStyle} />
|
|
136
133
|
</TouchableOpacity>
|
|
137
|
-
|
|
134
|
+
) : (
|
|
135
|
+
<View style={inputContainerStyle}>
|
|
136
|
+
<TextInput
|
|
137
|
+
value={inputValue}
|
|
138
|
+
onChangeText={handleInputChange}
|
|
139
|
+
onBlur={handleInputBlur}
|
|
140
|
+
placeholder={placeholder}
|
|
141
|
+
editable={!disabled}
|
|
142
|
+
style={textInputStyle}
|
|
143
|
+
/>
|
|
144
|
+
<TouchableOpacity
|
|
145
|
+
style={iconButtonStyle}
|
|
146
|
+
onPress={() => !disabled && setOpen(true)}
|
|
147
|
+
disabled={disabled}
|
|
148
|
+
>
|
|
149
|
+
<MaterialDesignIcons name="clock-outline" size={iconStyle.width} style={iconStyle} />
|
|
150
|
+
</TouchableOpacity>
|
|
151
|
+
</View>
|
|
152
|
+
)}
|
|
138
153
|
{error && (
|
|
139
154
|
<Text style={errorTextStyle}>
|
|
140
155
|
{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,
|
|
@@ -132,30 +133,50 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
132
133
|
const errorProps = getWebProps([errorTextStyle]);
|
|
133
134
|
const popoverProps = getWebProps([popoverContentStyle]);
|
|
134
135
|
|
|
136
|
+
// Pressable text style (reuses input style but renders as a button)
|
|
137
|
+
const pressableTextStyle = (styles.pressableText as any)({ disabled, size });
|
|
138
|
+
const pressableTextProps = getWebProps([pressableTextStyle]);
|
|
139
|
+
|
|
135
140
|
return (
|
|
136
141
|
<div style={style as React.CSSProperties}>
|
|
137
142
|
{label && (
|
|
138
143
|
<span {...labelProps}>{label}</span>
|
|
139
144
|
)}
|
|
140
|
-
|
|
141
|
-
<input
|
|
142
|
-
type="text"
|
|
143
|
-
value={inputValue}
|
|
144
|
-
onChange={handleInputChange}
|
|
145
|
-
onBlur={handleInputBlur}
|
|
146
|
-
placeholder={placeholder}
|
|
147
|
-
disabled={disabled}
|
|
148
|
-
{...inputProps}
|
|
149
|
-
/>
|
|
145
|
+
{pressable ? (
|
|
150
146
|
<button
|
|
151
147
|
type="button"
|
|
152
|
-
{...
|
|
148
|
+
{...containerProps}
|
|
149
|
+
ref={triggerRef as any}
|
|
150
|
+
style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}
|
|
153
151
|
onClick={() => !disabled && setOpen(!open)}
|
|
154
152
|
disabled={disabled}
|
|
155
153
|
>
|
|
154
|
+
<span {...pressableTextProps} style={!value ? { color: pressableTextStyle.placeholderColor } : undefined}>
|
|
155
|
+
{value ? formatTime(value) : placeholder}
|
|
156
|
+
</span>
|
|
156
157
|
<IconSvg path={mdiClockOutline} size={iconSize} color={iconColor} />
|
|
157
158
|
</button>
|
|
158
|
-
|
|
159
|
+
) : (
|
|
160
|
+
<div {...containerProps} ref={triggerRef}>
|
|
161
|
+
<input
|
|
162
|
+
type="text"
|
|
163
|
+
value={inputValue}
|
|
164
|
+
onChange={handleInputChange}
|
|
165
|
+
onBlur={handleInputBlur}
|
|
166
|
+
placeholder={placeholder}
|
|
167
|
+
disabled={disabled}
|
|
168
|
+
{...inputProps}
|
|
169
|
+
/>
|
|
170
|
+
<button
|
|
171
|
+
type="button"
|
|
172
|
+
{...iconButtonProps}
|
|
173
|
+
onClick={() => !disabled && setOpen(!open)}
|
|
174
|
+
disabled={disabled}
|
|
175
|
+
>
|
|
176
|
+
<IconSvg path={mdiClockOutline} size={iconSize} color={iconColor} />
|
|
177
|
+
</button>
|
|
178
|
+
</div>
|
|
179
|
+
)}
|
|
159
180
|
{error && (
|
|
160
181
|
<span {...errorProps}>{error}</span>
|
|
161
182
|
)}
|
package/src/index.native.ts
CHANGED
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -34,6 +34,8 @@ export interface DateInputProps {
|
|
|
34
34
|
minDate?: Date;
|
|
35
35
|
maxDate?: Date;
|
|
36
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;
|
|
37
39
|
error?: string;
|
|
38
40
|
size?: Size;
|
|
39
41
|
style?: ViewStyle;
|
|
@@ -47,6 +49,8 @@ export interface TimeInputProps {
|
|
|
47
49
|
mode?: '12h' | '24h';
|
|
48
50
|
minuteStep?: number;
|
|
49
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;
|
|
50
54
|
error?: string;
|
|
51
55
|
size?: Size;
|
|
52
56
|
style?: ViewStyle;
|
|
@@ -61,6 +65,8 @@ export interface DateTimePickerProps {
|
|
|
61
65
|
timeMode?: '12h' | '24h';
|
|
62
66
|
minuteStep?: number;
|
|
63
67
|
disabled?: boolean;
|
|
68
|
+
/** When true, both input areas are pressable to open their pickers instead of being text inputs. */
|
|
69
|
+
pressable?: boolean;
|
|
64
70
|
error?: string;
|
|
65
71
|
size?: Size;
|
|
66
72
|
style?: ViewStyle;
|