@idealyst/datepicker 1.1.6 → 1.1.7
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 +5 -5
- package/src/DateInput.native.tsx +11 -29
- package/src/DateInput.web.tsx +15 -22
- package/src/{DatePicker.tsx → DatePicker.native.tsx} +6 -17
- package/src/DatePicker.web.tsx +279 -0
- package/src/{DateTimePicker.tsx → DateTimePicker.native.tsx} +2 -1
- package/src/DateTimePicker.web.tsx +93 -0
- package/src/TimeInput.native.tsx +11 -29
- package/src/TimeInput.web.tsx +15 -22
- package/src/{TimePicker.tsx → TimePicker.native.tsx} +2 -1
- package/src/TimePicker.web.tsx +113 -0
- package/src/index.native.ts +3 -3
- package/src/index.ts +6 -6
- package/src/styles.ts +127 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/datepicker",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
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,8 +36,8 @@
|
|
|
36
36
|
"publish:npm": "npm publish"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@idealyst/components": "^1.1.
|
|
40
|
-
"@idealyst/theme": "^1.1.
|
|
39
|
+
"@idealyst/components": "^1.1.7",
|
|
40
|
+
"@idealyst/theme": "^1.1.7",
|
|
41
41
|
"react": ">=16.8.0",
|
|
42
42
|
"react-native": ">=0.60.0",
|
|
43
43
|
"react-native-svg": ">=13.0.0",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
}
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
-
"@idealyst/components": "^1.1.
|
|
65
|
-
"@idealyst/theme": "^1.1.
|
|
64
|
+
"@idealyst/components": "^1.1.7",
|
|
65
|
+
"@idealyst/theme": "^1.1.7",
|
|
66
66
|
"@types/react": "^19.1.0",
|
|
67
67
|
"react": "^19.1.0",
|
|
68
68
|
"react-native": "^0.80.1",
|
package/src/DateInput.native.tsx
CHANGED
|
@@ -69,38 +69,27 @@ export const DateInput: React.FC<DateInputProps> = ({
|
|
|
69
69
|
setOpen(false);
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
+
// Apply variants for input container
|
|
73
|
+
datePickerStyles.useVariants({
|
|
74
|
+
disabled,
|
|
75
|
+
error: !!error,
|
|
76
|
+
});
|
|
77
|
+
|
|
72
78
|
return (
|
|
73
79
|
<View style={style}>
|
|
74
80
|
{label && (
|
|
75
|
-
<Text typography="body2" weight="medium" style={
|
|
81
|
+
<Text typography="body2" weight="medium" style={datePickerStyles.labelText}>
|
|
76
82
|
{label}
|
|
77
83
|
</Text>
|
|
78
84
|
)}
|
|
79
|
-
<View
|
|
80
|
-
style={{
|
|
81
|
-
flexDirection: 'row',
|
|
82
|
-
alignItems: 'center',
|
|
83
|
-
borderWidth: 1,
|
|
84
|
-
borderColor: error ? '#ef4444' : '#d1d5db',
|
|
85
|
-
borderRadius: 6,
|
|
86
|
-
backgroundColor: disabled ? '#f3f4f6' : 'white',
|
|
87
|
-
overflow: 'hidden',
|
|
88
|
-
}}
|
|
89
|
-
>
|
|
85
|
+
<View style={datePickerStyles.inputContainer}>
|
|
90
86
|
<RNTextInput
|
|
91
87
|
value={inputValue}
|
|
92
88
|
onChangeText={handleInputChange}
|
|
93
89
|
onBlur={handleInputBlur}
|
|
94
90
|
placeholder={placeholder}
|
|
95
91
|
editable={!disabled}
|
|
96
|
-
style={
|
|
97
|
-
flex: 1,
|
|
98
|
-
padding: 8,
|
|
99
|
-
paddingHorizontal: 12,
|
|
100
|
-
fontSize: 14,
|
|
101
|
-
backgroundColor: 'transparent',
|
|
102
|
-
color: disabled ? '#9ca3af' : '#111827',
|
|
103
|
-
}}
|
|
92
|
+
style={datePickerStyles.textInput}
|
|
104
93
|
/>
|
|
105
94
|
<Button
|
|
106
95
|
type="text"
|
|
@@ -113,7 +102,7 @@ export const DateInput: React.FC<DateInputProps> = ({
|
|
|
113
102
|
</Button>
|
|
114
103
|
</View>
|
|
115
104
|
{error && (
|
|
116
|
-
<Text typography="caption" style={
|
|
105
|
+
<Text typography="caption" style={datePickerStyles.errorText}>
|
|
117
106
|
{error}
|
|
118
107
|
</Text>
|
|
119
108
|
)}
|
|
@@ -124,14 +113,7 @@ export const DateInput: React.FC<DateInputProps> = ({
|
|
|
124
113
|
animationType="fade"
|
|
125
114
|
onRequestClose={() => setOpen(false)}
|
|
126
115
|
>
|
|
127
|
-
<View
|
|
128
|
-
style={{
|
|
129
|
-
flex: 1,
|
|
130
|
-
justifyContent: 'center',
|
|
131
|
-
alignItems: 'center',
|
|
132
|
-
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
133
|
-
}}
|
|
134
|
-
>
|
|
116
|
+
<View style={datePickerStyles.modalBackdrop}>
|
|
135
117
|
<View style={datePickerStyles.popoverContent}>
|
|
136
118
|
<DatePicker
|
|
137
119
|
value={value ?? undefined}
|
package/src/DateInput.web.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { getWebProps } from 'react-native-unistyles/web';
|
|
2
3
|
import { View, Text, Button, Icon } from '@idealyst/components';
|
|
3
4
|
import { PositionedPortal } from '@idealyst/components/internal';
|
|
4
5
|
import { DatePicker } from './DatePicker';
|
|
@@ -71,24 +72,24 @@ export const DateInput: React.FC<DateInputProps> = ({
|
|
|
71
72
|
setOpen(false);
|
|
72
73
|
};
|
|
73
74
|
|
|
75
|
+
// Apply variants for input container
|
|
76
|
+
datePickerStyles.useVariants({
|
|
77
|
+
disabled,
|
|
78
|
+
error: !!error,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Get web props for styled elements
|
|
82
|
+
const containerProps = getWebProps([datePickerStyles.inputContainer]);
|
|
83
|
+
const inputProps = getWebProps([datePickerStyles.textInput]);
|
|
84
|
+
|
|
74
85
|
return (
|
|
75
86
|
<View style={style}>
|
|
76
87
|
{label && (
|
|
77
|
-
<Text typography="body2" weight="medium" style={
|
|
88
|
+
<Text typography="body2" weight="medium" style={datePickerStyles.labelText}>
|
|
78
89
|
{label}
|
|
79
90
|
</Text>
|
|
80
91
|
)}
|
|
81
|
-
<div
|
|
82
|
-
ref={triggerRef}
|
|
83
|
-
style={{
|
|
84
|
-
display: 'flex',
|
|
85
|
-
alignItems: 'center',
|
|
86
|
-
border: `1px solid ${error ? '#ef4444' : '#d1d5db'}`,
|
|
87
|
-
borderRadius: 6,
|
|
88
|
-
backgroundColor: disabled ? '#f3f4f6' : 'white',
|
|
89
|
-
overflow: 'hidden',
|
|
90
|
-
}}
|
|
91
|
-
>
|
|
92
|
+
<div ref={triggerRef} {...containerProps}>
|
|
92
93
|
<input
|
|
93
94
|
type="text"
|
|
94
95
|
value={inputValue}
|
|
@@ -96,15 +97,7 @@ export const DateInput: React.FC<DateInputProps> = ({
|
|
|
96
97
|
onBlur={handleInputBlur}
|
|
97
98
|
placeholder={placeholder}
|
|
98
99
|
disabled={disabled}
|
|
99
|
-
|
|
100
|
-
flex: 1,
|
|
101
|
-
padding: '8px 12px',
|
|
102
|
-
fontSize: 14,
|
|
103
|
-
border: 'none',
|
|
104
|
-
outline: 'none',
|
|
105
|
-
backgroundColor: 'transparent',
|
|
106
|
-
color: disabled ? '#9ca3af' : '#111827',
|
|
107
|
-
}}
|
|
100
|
+
{...inputProps}
|
|
108
101
|
/>
|
|
109
102
|
<Button
|
|
110
103
|
type="text"
|
|
@@ -117,7 +110,7 @@ export const DateInput: React.FC<DateInputProps> = ({
|
|
|
117
110
|
</Button>
|
|
118
111
|
</div>
|
|
119
112
|
{error && (
|
|
120
|
-
<Text typography="caption" style={
|
|
113
|
+
<Text typography="caption" style={datePickerStyles.errorText}>
|
|
121
114
|
{error}
|
|
122
115
|
</Text>
|
|
123
116
|
)}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useMemo, useState } from 'react';
|
|
2
|
-
import { View
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { Text, Button, Icon } from '@idealyst/components';
|
|
3
4
|
import { datePickerStyles } from './styles';
|
|
4
5
|
import type { DatePickerProps } from './types';
|
|
5
6
|
|
|
@@ -21,7 +22,7 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
21
22
|
|
|
22
23
|
datePickerStyles.useVariants({ disabled });
|
|
23
24
|
|
|
24
|
-
const { days,
|
|
25
|
+
const { days, monthShort, year } = useMemo(() => {
|
|
25
26
|
const year = currentMonth.getFullYear();
|
|
26
27
|
const month = currentMonth.getMonth();
|
|
27
28
|
const firstDay = new Date(year, month, 1);
|
|
@@ -53,12 +54,7 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
month: 'short',
|
|
58
|
-
year: 'numeric',
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
return { days, monthLabel, monthShort: MONTHS[month], year };
|
|
57
|
+
return { days, monthShort: MONTHS[month], year };
|
|
62
58
|
}, [currentMonth]);
|
|
63
59
|
|
|
64
60
|
const isSelected = (date: Date): boolean => {
|
|
@@ -240,16 +236,9 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
240
236
|
key={index}
|
|
241
237
|
style={[
|
|
242
238
|
datePickerStyles.dayCell,
|
|
243
|
-
selected &&
|
|
244
|
-
backgroundColor: '#3b82f6',
|
|
245
|
-
borderRadius: 4,
|
|
246
|
-
},
|
|
239
|
+
selected && datePickerStyles.selectedDay,
|
|
247
240
|
!isCurrentMonth && { opacity: 0.3 },
|
|
248
|
-
today && !selected &&
|
|
249
|
-
borderWidth: 1,
|
|
250
|
-
borderColor: '#3b82f6',
|
|
251
|
-
borderRadius: 4,
|
|
252
|
-
},
|
|
241
|
+
today && !selected && datePickerStyles.todayDay,
|
|
253
242
|
dayDisabled && { opacity: 0.3 },
|
|
254
243
|
]}
|
|
255
244
|
>
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import React, { useMemo, useState } from 'react';
|
|
2
|
+
import { getWebProps } from 'react-native-unistyles/web';
|
|
3
|
+
import { Text, Button, Icon } from '@idealyst/components';
|
|
4
|
+
import { datePickerStyles } from './styles';
|
|
5
|
+
import type { DatePickerProps } from './types';
|
|
6
|
+
|
|
7
|
+
const WEEKDAYS = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
|
|
8
|
+
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
9
|
+
|
|
10
|
+
type ViewMode = 'calendar' | 'months' | 'years';
|
|
11
|
+
|
|
12
|
+
export const DatePicker: React.FC<DatePickerProps> = ({
|
|
13
|
+
value,
|
|
14
|
+
onChange,
|
|
15
|
+
minDate,
|
|
16
|
+
maxDate,
|
|
17
|
+
disabled = false,
|
|
18
|
+
style,
|
|
19
|
+
}) => {
|
|
20
|
+
const [currentMonth, setCurrentMonth] = useState(() => value || new Date());
|
|
21
|
+
const [viewMode, setViewMode] = useState<ViewMode>('calendar');
|
|
22
|
+
|
|
23
|
+
datePickerStyles.useVariants({ disabled });
|
|
24
|
+
|
|
25
|
+
const { days, monthShort, year } = useMemo(() => {
|
|
26
|
+
const year = currentMonth.getFullYear();
|
|
27
|
+
const month = currentMonth.getMonth();
|
|
28
|
+
const firstDay = new Date(year, month, 1);
|
|
29
|
+
const lastDay = new Date(year, month + 1, 0);
|
|
30
|
+
const startPadding = firstDay.getDay();
|
|
31
|
+
const daysInMonth = lastDay.getDate();
|
|
32
|
+
|
|
33
|
+
const days: Array<{ date: Date; isCurrentMonth: boolean }> = [];
|
|
34
|
+
|
|
35
|
+
// Previous month padding
|
|
36
|
+
const prevMonthEnd = new Date(year, month, 0);
|
|
37
|
+
for (let i = startPadding - 1; i >= 0; i--) {
|
|
38
|
+
days.push({
|
|
39
|
+
date: new Date(year, month - 1, prevMonthEnd.getDate() - i),
|
|
40
|
+
isCurrentMonth: false,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Current month
|
|
45
|
+
for (let day = 1; day <= daysInMonth; day++) {
|
|
46
|
+
days.push({ date: new Date(year, month, day), isCurrentMonth: true });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Next month padding (fill to complete last week)
|
|
50
|
+
const remaining = 7 - (days.length % 7);
|
|
51
|
+
if (remaining < 7) {
|
|
52
|
+
for (let day = 1; day <= remaining; day++) {
|
|
53
|
+
days.push({ date: new Date(year, month + 1, day), isCurrentMonth: false });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { days, monthShort: MONTHS[month], year };
|
|
58
|
+
}, [currentMonth]);
|
|
59
|
+
|
|
60
|
+
const isSelected = (date: Date): boolean => {
|
|
61
|
+
if (!value) return false;
|
|
62
|
+
return (
|
|
63
|
+
date.getDate() === value.getDate() &&
|
|
64
|
+
date.getMonth() === value.getMonth() &&
|
|
65
|
+
date.getFullYear() === value.getFullYear()
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const isToday = (date: Date): boolean => {
|
|
70
|
+
const today = new Date();
|
|
71
|
+
return (
|
|
72
|
+
date.getDate() === today.getDate() &&
|
|
73
|
+
date.getMonth() === today.getMonth() &&
|
|
74
|
+
date.getFullYear() === today.getFullYear()
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const isDisabled = (date: Date): boolean => {
|
|
79
|
+
if (disabled) return true;
|
|
80
|
+
if (minDate && date < new Date(minDate.setHours(0, 0, 0, 0))) return true;
|
|
81
|
+
if (maxDate && date > new Date(maxDate.setHours(23, 59, 59, 999))) return true;
|
|
82
|
+
return false;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const goToPrevMonth = () => {
|
|
86
|
+
setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1));
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const goToNextMonth = () => {
|
|
90
|
+
setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1));
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const handleDayPress = (date: Date) => {
|
|
94
|
+
if (!isDisabled(date)) {
|
|
95
|
+
onChange(date);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const handleMonthSelect = (monthIndex: number) => {
|
|
100
|
+
setCurrentMonth(new Date(currentMonth.getFullYear(), monthIndex, 1));
|
|
101
|
+
setViewMode('calendar');
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const handleYearSelect = (selectedYear: number) => {
|
|
105
|
+
setCurrentMonth(new Date(selectedYear, currentMonth.getMonth(), 1));
|
|
106
|
+
setViewMode('months');
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Generate year range (10 years before and after current)
|
|
110
|
+
const yearRange = useMemo(() => {
|
|
111
|
+
const currentYear = currentMonth.getFullYear();
|
|
112
|
+
const startYear = Math.floor(currentYear / 10) * 10 - 10;
|
|
113
|
+
return Array.from({ length: 21 }, (_, i) => startYear + i);
|
|
114
|
+
}, [currentMonth]);
|
|
115
|
+
|
|
116
|
+
const goToPrevYearRange = () => {
|
|
117
|
+
setCurrentMonth(new Date(currentMonth.getFullYear() - 10, currentMonth.getMonth(), 1));
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const goToNextYearRange = () => {
|
|
121
|
+
setCurrentMonth(new Date(currentMonth.getFullYear() + 10, currentMonth.getMonth(), 1));
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Get web props for styled elements
|
|
125
|
+
const calendarProps = getWebProps([datePickerStyles.calendar, style as any]);
|
|
126
|
+
const headerProps = getWebProps([datePickerStyles.calendarHeader]);
|
|
127
|
+
const weekdayRowProps = getWebProps([datePickerStyles.weekdayRow]);
|
|
128
|
+
const weekdayCellProps = getWebProps([datePickerStyles.weekdayCell]);
|
|
129
|
+
const gridProps = getWebProps([datePickerStyles.calendarGrid]);
|
|
130
|
+
const monthGridProps = getWebProps([datePickerStyles.monthGrid]);
|
|
131
|
+
const yearGridProps = getWebProps([datePickerStyles.yearGrid]);
|
|
132
|
+
|
|
133
|
+
// Render month selector
|
|
134
|
+
if (viewMode === 'months') {
|
|
135
|
+
return (
|
|
136
|
+
<div {...calendarProps}>
|
|
137
|
+
<div {...headerProps}>
|
|
138
|
+
<Button type="text" size="sm" onPress={() => setViewMode('calendar')} disabled={disabled}>
|
|
139
|
+
<Icon name="chevron-left" size={16} />
|
|
140
|
+
</Button>
|
|
141
|
+
<Button type="text" size="sm" onPress={() => setViewMode('years')} disabled={disabled}>
|
|
142
|
+
<Text typography="body2" weight="semibold">{year}</Text>
|
|
143
|
+
</Button>
|
|
144
|
+
<div style={{ width: 28 }} />
|
|
145
|
+
</div>
|
|
146
|
+
<div {...monthGridProps}>
|
|
147
|
+
{MONTHS.map((month, index) => {
|
|
148
|
+
const isCurrentMonth = index === currentMonth.getMonth();
|
|
149
|
+
return (
|
|
150
|
+
<Button
|
|
151
|
+
key={month}
|
|
152
|
+
type={isCurrentMonth ? 'contained' : 'text'}
|
|
153
|
+
size="sm"
|
|
154
|
+
onPress={() => handleMonthSelect(index)}
|
|
155
|
+
disabled={disabled}
|
|
156
|
+
style={{ minWidth: 48, margin: 2 }}
|
|
157
|
+
>
|
|
158
|
+
<Text typography="caption" color={isCurrentMonth ? 'inverse' : 'primary'}>
|
|
159
|
+
{month}
|
|
160
|
+
</Text>
|
|
161
|
+
</Button>
|
|
162
|
+
);
|
|
163
|
+
})}
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Render year selector
|
|
170
|
+
if (viewMode === 'years') {
|
|
171
|
+
return (
|
|
172
|
+
<div {...calendarProps}>
|
|
173
|
+
<div {...headerProps}>
|
|
174
|
+
<Button type="text" size="sm" onPress={goToPrevYearRange} disabled={disabled}>
|
|
175
|
+
<Icon name="chevron-left" size={16} />
|
|
176
|
+
</Button>
|
|
177
|
+
<Text typography="body2" weight="semibold">
|
|
178
|
+
{yearRange[0]} - {yearRange[yearRange.length - 1]}
|
|
179
|
+
</Text>
|
|
180
|
+
<Button type="text" size="sm" onPress={goToNextYearRange} disabled={disabled}>
|
|
181
|
+
<Icon name="chevron-right" size={16} />
|
|
182
|
+
</Button>
|
|
183
|
+
</div>
|
|
184
|
+
<div {...yearGridProps}>
|
|
185
|
+
{yearRange.map((yr) => {
|
|
186
|
+
const isCurrentYear = yr === currentMonth.getFullYear();
|
|
187
|
+
return (
|
|
188
|
+
<Button
|
|
189
|
+
key={yr}
|
|
190
|
+
type={isCurrentYear ? 'contained' : 'text'}
|
|
191
|
+
size="sm"
|
|
192
|
+
onPress={() => handleYearSelect(yr)}
|
|
193
|
+
disabled={disabled}
|
|
194
|
+
style={{ minWidth: 48, margin: 2 }}
|
|
195
|
+
>
|
|
196
|
+
<Text typography="caption" color={isCurrentYear ? 'inverse' : 'primary'}>
|
|
197
|
+
{yr}
|
|
198
|
+
</Text>
|
|
199
|
+
</Button>
|
|
200
|
+
);
|
|
201
|
+
})}
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Render calendar (default)
|
|
208
|
+
return (
|
|
209
|
+
<div {...calendarProps}>
|
|
210
|
+
{/* Header */}
|
|
211
|
+
<div {...headerProps}>
|
|
212
|
+
<Button type="text" size="sm" onPress={goToPrevMonth} disabled={disabled}>
|
|
213
|
+
<Icon name="chevron-left" size={16} />
|
|
214
|
+
</Button>
|
|
215
|
+
<Button type="text" size="sm" onPress={() => setViewMode('months')} disabled={disabled}>
|
|
216
|
+
<Text typography="body2" weight="semibold">
|
|
217
|
+
{monthShort} {year}
|
|
218
|
+
</Text>
|
|
219
|
+
</Button>
|
|
220
|
+
<Button type="text" size="sm" onPress={goToNextMonth} disabled={disabled}>
|
|
221
|
+
<Icon name="chevron-right" size={16} />
|
|
222
|
+
</Button>
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
{/* Weekday headers */}
|
|
226
|
+
<div {...weekdayRowProps}>
|
|
227
|
+
{WEEKDAYS.map((day, i) => (
|
|
228
|
+
<div key={i} {...weekdayCellProps}>
|
|
229
|
+
<Text typography="caption" color="secondary">
|
|
230
|
+
{day}
|
|
231
|
+
</Text>
|
|
232
|
+
</div>
|
|
233
|
+
))}
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
{/* Calendar grid */}
|
|
237
|
+
<div {...gridProps}>
|
|
238
|
+
{days.map(({ date, isCurrentMonth }, index) => {
|
|
239
|
+
const selected = isSelected(date);
|
|
240
|
+
const today = isToday(date);
|
|
241
|
+
const dayDisabled = isDisabled(date);
|
|
242
|
+
|
|
243
|
+
const dayCellProps = getWebProps([
|
|
244
|
+
datePickerStyles.dayCell,
|
|
245
|
+
selected && datePickerStyles.selectedDay,
|
|
246
|
+
today && !selected && datePickerStyles.todayDay,
|
|
247
|
+
].filter(Boolean));
|
|
248
|
+
|
|
249
|
+
const cellStyle: React.CSSProperties = {
|
|
250
|
+
opacity: (!isCurrentMonth || dayDisabled) ? 0.3 : 1,
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<div
|
|
255
|
+
key={index}
|
|
256
|
+
{...dayCellProps}
|
|
257
|
+
style={cellStyle}
|
|
258
|
+
>
|
|
259
|
+
<Button
|
|
260
|
+
type="text"
|
|
261
|
+
size="sm"
|
|
262
|
+
onPress={() => handleDayPress(date)}
|
|
263
|
+
disabled={dayDisabled}
|
|
264
|
+
style={{ minWidth: 24, minHeight: 24, padding: 0 }}
|
|
265
|
+
>
|
|
266
|
+
<Text
|
|
267
|
+
typography="caption"
|
|
268
|
+
color={selected ? 'inverse' : 'primary'}
|
|
269
|
+
>
|
|
270
|
+
{date.getDate()}
|
|
271
|
+
</Text>
|
|
272
|
+
</Button>
|
|
273
|
+
</div>
|
|
274
|
+
);
|
|
275
|
+
})}
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
);
|
|
279
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { View
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { Text } from '@idealyst/components';
|
|
3
4
|
import { DateInput } from './DateInput';
|
|
4
5
|
import { TimeInput } from './TimeInput';
|
|
5
6
|
import { datePickerStyles } from './styles';
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { getWebProps } from 'react-native-unistyles/web';
|
|
3
|
+
import { Text } from '@idealyst/components';
|
|
4
|
+
import { DateInput } from './DateInput';
|
|
5
|
+
import { TimeInput } from './TimeInput';
|
|
6
|
+
import { datePickerStyles } from './styles';
|
|
7
|
+
import type { DateTimePickerProps } from './types';
|
|
8
|
+
|
|
9
|
+
export const DateTimePicker: React.FC<DateTimePickerProps> = ({
|
|
10
|
+
value,
|
|
11
|
+
onChange,
|
|
12
|
+
label,
|
|
13
|
+
minDate,
|
|
14
|
+
maxDate,
|
|
15
|
+
timeMode = '12h',
|
|
16
|
+
minuteStep = 1,
|
|
17
|
+
disabled = false,
|
|
18
|
+
error,
|
|
19
|
+
style,
|
|
20
|
+
}) => {
|
|
21
|
+
const handleDateChange = (date: Date | null) => {
|
|
22
|
+
if (!date) {
|
|
23
|
+
onChange(null);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// Preserve time from current value, or use noon as default
|
|
27
|
+
const hours = value?.getHours() ?? 12;
|
|
28
|
+
const minutes = value?.getMinutes() ?? 0;
|
|
29
|
+
const updated = new Date(date);
|
|
30
|
+
updated.setHours(hours, minutes, 0, 0);
|
|
31
|
+
onChange(updated);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const handleTimeChange = (time: Date | null) => {
|
|
35
|
+
if (!time) {
|
|
36
|
+
// Only clear time component, keep date if it exists
|
|
37
|
+
if (value) {
|
|
38
|
+
const updated = new Date(value);
|
|
39
|
+
updated.setHours(12, 0, 0, 0);
|
|
40
|
+
onChange(updated);
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Preserve date from current value, or use today
|
|
45
|
+
const baseDate = value || new Date();
|
|
46
|
+
const updated = new Date(
|
|
47
|
+
baseDate.getFullYear(),
|
|
48
|
+
baseDate.getMonth(),
|
|
49
|
+
baseDate.getDate(),
|
|
50
|
+
time.getHours(),
|
|
51
|
+
time.getMinutes(),
|
|
52
|
+
0,
|
|
53
|
+
0
|
|
54
|
+
);
|
|
55
|
+
onChange(updated);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Get web props for styled elements
|
|
59
|
+
const inputRowProps = getWebProps([datePickerStyles.inputRow]);
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div style={style as React.CSSProperties}>
|
|
63
|
+
{label && (
|
|
64
|
+
<Text typography="body2" weight="medium" style={{ marginBottom: 4 }}>
|
|
65
|
+
{label}
|
|
66
|
+
</Text>
|
|
67
|
+
)}
|
|
68
|
+
<div {...inputRowProps}>
|
|
69
|
+
<div style={{ flex: 1 }}>
|
|
70
|
+
<DateInput
|
|
71
|
+
value={value ?? undefined}
|
|
72
|
+
onChange={handleDateChange}
|
|
73
|
+
placeholder="MM/DD/YYYY"
|
|
74
|
+
minDate={minDate}
|
|
75
|
+
maxDate={maxDate}
|
|
76
|
+
disabled={disabled}
|
|
77
|
+
error={error}
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
<div style={{ flex: 1 }}>
|
|
81
|
+
<TimeInput
|
|
82
|
+
value={value ?? undefined}
|
|
83
|
+
onChange={handleTimeChange}
|
|
84
|
+
placeholder={timeMode === '24h' ? '14:30' : '2:30 PM'}
|
|
85
|
+
mode={timeMode}
|
|
86
|
+
minuteStep={minuteStep}
|
|
87
|
+
disabled={disabled}
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
};
|
package/src/TimeInput.native.tsx
CHANGED
|
@@ -89,38 +89,27 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
89
89
|
onChange(date);
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
+
// Apply variants for input container
|
|
93
|
+
datePickerStyles.useVariants({
|
|
94
|
+
disabled,
|
|
95
|
+
error: !!error,
|
|
96
|
+
});
|
|
97
|
+
|
|
92
98
|
return (
|
|
93
99
|
<View style={style}>
|
|
94
100
|
{label && (
|
|
95
|
-
<Text typography="body2" weight="medium" style={
|
|
101
|
+
<Text typography="body2" weight="medium" style={datePickerStyles.labelText}>
|
|
96
102
|
{label}
|
|
97
103
|
</Text>
|
|
98
104
|
)}
|
|
99
|
-
<View
|
|
100
|
-
style={{
|
|
101
|
-
flexDirection: 'row',
|
|
102
|
-
alignItems: 'center',
|
|
103
|
-
borderWidth: 1,
|
|
104
|
-
borderColor: error ? '#ef4444' : '#d1d5db',
|
|
105
|
-
borderRadius: 6,
|
|
106
|
-
backgroundColor: disabled ? '#f3f4f6' : 'white',
|
|
107
|
-
overflow: 'hidden',
|
|
108
|
-
}}
|
|
109
|
-
>
|
|
105
|
+
<View style={datePickerStyles.inputContainer}>
|
|
110
106
|
<RNTextInput
|
|
111
107
|
value={inputValue}
|
|
112
108
|
onChangeText={handleInputChange}
|
|
113
109
|
onBlur={handleInputBlur}
|
|
114
110
|
placeholder={placeholder}
|
|
115
111
|
editable={!disabled}
|
|
116
|
-
style={
|
|
117
|
-
flex: 1,
|
|
118
|
-
padding: 8,
|
|
119
|
-
paddingHorizontal: 12,
|
|
120
|
-
fontSize: 14,
|
|
121
|
-
backgroundColor: 'transparent',
|
|
122
|
-
color: disabled ? '#9ca3af' : '#111827',
|
|
123
|
-
}}
|
|
112
|
+
style={datePickerStyles.textInput}
|
|
124
113
|
/>
|
|
125
114
|
<Button
|
|
126
115
|
type="text"
|
|
@@ -133,7 +122,7 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
133
122
|
</Button>
|
|
134
123
|
</View>
|
|
135
124
|
{error && (
|
|
136
|
-
<Text typography="caption" style={
|
|
125
|
+
<Text typography="caption" style={datePickerStyles.errorText}>
|
|
137
126
|
{error}
|
|
138
127
|
</Text>
|
|
139
128
|
)}
|
|
@@ -144,14 +133,7 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
144
133
|
animationType="fade"
|
|
145
134
|
onRequestClose={() => setOpen(false)}
|
|
146
135
|
>
|
|
147
|
-
<View
|
|
148
|
-
style={{
|
|
149
|
-
flex: 1,
|
|
150
|
-
justifyContent: 'center',
|
|
151
|
-
alignItems: 'center',
|
|
152
|
-
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
153
|
-
}}
|
|
154
|
-
>
|
|
136
|
+
<View style={datePickerStyles.modalBackdrop}>
|
|
155
137
|
<View style={datePickerStyles.popoverContent}>
|
|
156
138
|
<TimePicker
|
|
157
139
|
value={value ?? undefined}
|
package/src/TimeInput.web.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { getWebProps } from 'react-native-unistyles/web';
|
|
2
3
|
import { View, Text, Button, Icon } from '@idealyst/components';
|
|
3
4
|
import { PositionedPortal } from '@idealyst/components/internal';
|
|
4
5
|
import { TimePicker } from './TimePicker';
|
|
@@ -96,24 +97,24 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
96
97
|
onChange(date);
|
|
97
98
|
};
|
|
98
99
|
|
|
100
|
+
// Apply variants for input container
|
|
101
|
+
datePickerStyles.useVariants({
|
|
102
|
+
disabled,
|
|
103
|
+
error: !!error,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Get web props for styled elements
|
|
107
|
+
const containerProps = getWebProps([datePickerStyles.inputContainer]);
|
|
108
|
+
const inputProps = getWebProps([datePickerStyles.textInput]);
|
|
109
|
+
|
|
99
110
|
return (
|
|
100
111
|
<View style={style}>
|
|
101
112
|
{label && (
|
|
102
|
-
<Text typography="body2" weight="medium" style={
|
|
113
|
+
<Text typography="body2" weight="medium" style={datePickerStyles.labelText}>
|
|
103
114
|
{label}
|
|
104
115
|
</Text>
|
|
105
116
|
)}
|
|
106
|
-
<div
|
|
107
|
-
ref={triggerRef}
|
|
108
|
-
style={{
|
|
109
|
-
display: 'flex',
|
|
110
|
-
alignItems: 'center',
|
|
111
|
-
border: `1px solid ${error ? '#ef4444' : '#d1d5db'}`,
|
|
112
|
-
borderRadius: 6,
|
|
113
|
-
backgroundColor: disabled ? '#f3f4f6' : 'white',
|
|
114
|
-
overflow: 'hidden',
|
|
115
|
-
}}
|
|
116
|
-
>
|
|
117
|
+
<div ref={triggerRef} {...containerProps}>
|
|
117
118
|
<input
|
|
118
119
|
type="text"
|
|
119
120
|
value={inputValue}
|
|
@@ -121,15 +122,7 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
121
122
|
onBlur={handleInputBlur}
|
|
122
123
|
placeholder={placeholder}
|
|
123
124
|
disabled={disabled}
|
|
124
|
-
|
|
125
|
-
flex: 1,
|
|
126
|
-
padding: '8px 12px',
|
|
127
|
-
fontSize: 14,
|
|
128
|
-
border: 'none',
|
|
129
|
-
outline: 'none',
|
|
130
|
-
backgroundColor: 'transparent',
|
|
131
|
-
color: disabled ? '#9ca3af' : '#111827',
|
|
132
|
-
}}
|
|
125
|
+
{...inputProps}
|
|
133
126
|
/>
|
|
134
127
|
<Button
|
|
135
128
|
type="text"
|
|
@@ -142,7 +135,7 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
142
135
|
</Button>
|
|
143
136
|
</div>
|
|
144
137
|
{error && (
|
|
145
|
-
<Text typography="caption" style={
|
|
138
|
+
<Text typography="caption" style={datePickerStyles.errorText}>
|
|
146
139
|
{error}
|
|
147
140
|
</Text>
|
|
148
141
|
)}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { View
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { Text, Button, Icon } from '@idealyst/components';
|
|
3
4
|
import { datePickerStyles } from './styles';
|
|
4
5
|
import type { TimePickerProps } from './types';
|
|
5
6
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { getWebProps } from 'react-native-unistyles/web';
|
|
3
|
+
import { Text, Button, Icon } from '@idealyst/components';
|
|
4
|
+
import { datePickerStyles } from './styles';
|
|
5
|
+
import type { TimePickerProps } from './types';
|
|
6
|
+
|
|
7
|
+
export const TimePicker: React.FC<TimePickerProps> = ({
|
|
8
|
+
value,
|
|
9
|
+
onChange,
|
|
10
|
+
mode = '12h',
|
|
11
|
+
minuteStep = 1,
|
|
12
|
+
disabled = false,
|
|
13
|
+
style,
|
|
14
|
+
}) => {
|
|
15
|
+
datePickerStyles.useVariants({ disabled });
|
|
16
|
+
|
|
17
|
+
const currentDate = value || new Date();
|
|
18
|
+
const hours = currentDate.getHours();
|
|
19
|
+
const minutes = currentDate.getMinutes();
|
|
20
|
+
const is12Hour = mode === '12h';
|
|
21
|
+
const isPM = hours >= 12;
|
|
22
|
+
const displayHours = is12Hour ? (hours % 12 || 12) : hours;
|
|
23
|
+
|
|
24
|
+
const updateTime = (newHours: number, newMinutes: number) => {
|
|
25
|
+
const updated = new Date(currentDate);
|
|
26
|
+
updated.setHours(newHours, newMinutes, 0, 0);
|
|
27
|
+
onChange(updated);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const incrementHours = () => {
|
|
31
|
+
const newHours = (hours + 1) % 24;
|
|
32
|
+
updateTime(newHours, minutes);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const decrementHours = () => {
|
|
36
|
+
const newHours = (hours - 1 + 24) % 24;
|
|
37
|
+
updateTime(newHours, minutes);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const incrementMinutes = () => {
|
|
41
|
+
const newMinutes = (minutes + minuteStep) % 60;
|
|
42
|
+
const hourChange = minutes + minuteStep >= 60 ? 1 : 0;
|
|
43
|
+
updateTime((hours + hourChange) % 24, newMinutes);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const decrementMinutes = () => {
|
|
47
|
+
const newMinutes = (minutes - minuteStep + 60) % 60;
|
|
48
|
+
const hourChange = minutes - minuteStep < 0 ? -1 : 0;
|
|
49
|
+
updateTime((hours + hourChange + 24) % 24, newMinutes);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const togglePeriod = () => {
|
|
53
|
+
const newHours = isPM ? hours - 12 : hours + 12;
|
|
54
|
+
updateTime(newHours, minutes);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Get web props for styled elements
|
|
58
|
+
const timePickerProps = getWebProps([datePickerStyles.timePicker, style as any]);
|
|
59
|
+
const timeColumnsProps = getWebProps([datePickerStyles.timeColumns]);
|
|
60
|
+
const timeColumnProps = getWebProps([datePickerStyles.timeColumn]);
|
|
61
|
+
const timeSeparatorProps = getWebProps([datePickerStyles.timeSeparator]);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div {...timePickerProps}>
|
|
65
|
+
<div {...timeColumnsProps}>
|
|
66
|
+
{/* Hours column */}
|
|
67
|
+
<div {...timeColumnProps}>
|
|
68
|
+
<Button type="text" size="sm" onPress={incrementHours} disabled={disabled}>
|
|
69
|
+
<Icon name="chevron-up" size={20} />
|
|
70
|
+
</Button>
|
|
71
|
+
<Text typography="h3" weight="semibold">
|
|
72
|
+
{String(displayHours).padStart(2, '0')}
|
|
73
|
+
</Text>
|
|
74
|
+
<Button type="text" size="sm" onPress={decrementHours} disabled={disabled}>
|
|
75
|
+
<Icon name="chevron-down" size={20} />
|
|
76
|
+
</Button>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
{/* Separator */}
|
|
80
|
+
<div {...timeSeparatorProps}>
|
|
81
|
+
<Text typography="h3" weight="semibold">:</Text>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
{/* Minutes column */}
|
|
85
|
+
<div {...timeColumnProps}>
|
|
86
|
+
<Button type="text" size="sm" onPress={incrementMinutes} disabled={disabled}>
|
|
87
|
+
<Icon name="chevron-up" size={20} />
|
|
88
|
+
</Button>
|
|
89
|
+
<Text typography="h3" weight="semibold">
|
|
90
|
+
{String(minutes).padStart(2, '0')}
|
|
91
|
+
</Text>
|
|
92
|
+
<Button type="text" size="sm" onPress={decrementMinutes} disabled={disabled}>
|
|
93
|
+
<Icon name="chevron-down" size={20} />
|
|
94
|
+
</Button>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
{/* AM/PM toggle for 12-hour mode */}
|
|
98
|
+
{is12Hour && (
|
|
99
|
+
<div {...timeColumnProps}>
|
|
100
|
+
<Button
|
|
101
|
+
type="outlined"
|
|
102
|
+
size="sm"
|
|
103
|
+
onPress={togglePeriod}
|
|
104
|
+
disabled={disabled}
|
|
105
|
+
>
|
|
106
|
+
{isPM ? 'PM' : 'AM'}
|
|
107
|
+
</Button>
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
};
|
package/src/index.native.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Components - native platform
|
|
2
|
-
export { DatePicker } from './DatePicker';
|
|
3
|
-
export { TimePicker } from './TimePicker';
|
|
2
|
+
export { DatePicker } from './DatePicker.native';
|
|
3
|
+
export { TimePicker } from './TimePicker.native';
|
|
4
4
|
export { DateInput } from './DateInput.native';
|
|
5
5
|
export { TimeInput } from './TimeInput.native';
|
|
6
|
-
export { DateTimePicker } from './DateTimePicker';
|
|
6
|
+
export { DateTimePicker } from './DateTimePicker.native';
|
|
7
7
|
|
|
8
8
|
// Types
|
|
9
9
|
export type {
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// Components
|
|
2
|
-
export { DatePicker } from './DatePicker';
|
|
3
|
-
export { TimePicker } from './TimePicker';
|
|
4
|
-
export { DateInput } from './DateInput';
|
|
5
|
-
export { TimeInput } from './TimeInput';
|
|
6
|
-
export { DateTimePicker } from './DateTimePicker';
|
|
1
|
+
// Components - web platform
|
|
2
|
+
export { DatePicker } from './DatePicker.web';
|
|
3
|
+
export { TimePicker } from './TimePicker.web';
|
|
4
|
+
export { DateInput } from './DateInput.web';
|
|
5
|
+
export { TimeInput } from './TimeInput.web';
|
|
6
|
+
export { DateTimePicker } from './DateTimePicker.web';
|
|
7
7
|
|
|
8
8
|
// Types
|
|
9
9
|
export type {
|
package/src/styles.ts
CHANGED
|
@@ -5,6 +5,11 @@ export type DatePickerVariants = {
|
|
|
5
5
|
disabled: boolean;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
+
export type InputContainerVariants = {
|
|
9
|
+
disabled: boolean;
|
|
10
|
+
error: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
8
13
|
export const datePickerStyles = StyleSheet.create((theme: Theme) => {
|
|
9
14
|
return {
|
|
10
15
|
// Calendar container - compact
|
|
@@ -28,18 +33,27 @@ export const datePickerStyles = StyleSheet.create((theme: Theme) => {
|
|
|
28
33
|
justifyContent: 'space-between',
|
|
29
34
|
marginBottom: 4,
|
|
30
35
|
paddingHorizontal: 2,
|
|
36
|
+
_web: {
|
|
37
|
+
display: 'flex',
|
|
38
|
+
},
|
|
31
39
|
},
|
|
32
40
|
|
|
33
41
|
calendarTitle: {
|
|
34
42
|
flexDirection: 'row',
|
|
35
43
|
alignItems: 'center',
|
|
36
44
|
gap: 2,
|
|
45
|
+
_web: {
|
|
46
|
+
display: 'flex',
|
|
47
|
+
},
|
|
37
48
|
},
|
|
38
49
|
|
|
39
50
|
// Weekday header row
|
|
40
51
|
weekdayRow: {
|
|
41
52
|
flexDirection: 'row',
|
|
42
53
|
marginBottom: 2,
|
|
54
|
+
_web: {
|
|
55
|
+
display: 'flex',
|
|
56
|
+
},
|
|
43
57
|
},
|
|
44
58
|
|
|
45
59
|
weekdayCell: {
|
|
@@ -47,12 +61,18 @@ export const datePickerStyles = StyleSheet.create((theme: Theme) => {
|
|
|
47
61
|
height: 20,
|
|
48
62
|
alignItems: 'center',
|
|
49
63
|
justifyContent: 'center',
|
|
64
|
+
_web: {
|
|
65
|
+
display: 'flex',
|
|
66
|
+
},
|
|
50
67
|
},
|
|
51
68
|
|
|
52
69
|
// Calendar grid
|
|
53
70
|
calendarGrid: {
|
|
54
71
|
flexDirection: 'row',
|
|
55
72
|
flexWrap: 'wrap',
|
|
73
|
+
_web: {
|
|
74
|
+
display: 'flex',
|
|
75
|
+
},
|
|
56
76
|
},
|
|
57
77
|
|
|
58
78
|
// Month selector grid (3x4)
|
|
@@ -61,6 +81,9 @@ export const datePickerStyles = StyleSheet.create((theme: Theme) => {
|
|
|
61
81
|
flexWrap: 'wrap',
|
|
62
82
|
justifyContent: 'center',
|
|
63
83
|
paddingVertical: 8,
|
|
84
|
+
_web: {
|
|
85
|
+
display: 'flex',
|
|
86
|
+
},
|
|
64
87
|
},
|
|
65
88
|
|
|
66
89
|
// Year selector grid
|
|
@@ -69,6 +92,9 @@ export const datePickerStyles = StyleSheet.create((theme: Theme) => {
|
|
|
69
92
|
flexWrap: 'wrap',
|
|
70
93
|
justifyContent: 'center',
|
|
71
94
|
paddingVertical: 8,
|
|
95
|
+
_web: {
|
|
96
|
+
display: 'flex',
|
|
97
|
+
},
|
|
72
98
|
},
|
|
73
99
|
|
|
74
100
|
// Individual day cell - compact
|
|
@@ -77,6 +103,9 @@ export const datePickerStyles = StyleSheet.create((theme: Theme) => {
|
|
|
77
103
|
height: 28,
|
|
78
104
|
alignItems: 'center',
|
|
79
105
|
justifyContent: 'center',
|
|
106
|
+
_web: {
|
|
107
|
+
display: 'flex',
|
|
108
|
+
},
|
|
80
109
|
},
|
|
81
110
|
|
|
82
111
|
// Time picker container
|
|
@@ -97,12 +126,18 @@ export const datePickerStyles = StyleSheet.create((theme: Theme) => {
|
|
|
97
126
|
flexDirection: 'row',
|
|
98
127
|
gap: 8,
|
|
99
128
|
alignItems: 'center',
|
|
129
|
+
_web: {
|
|
130
|
+
display: 'flex',
|
|
131
|
+
},
|
|
100
132
|
},
|
|
101
133
|
|
|
102
134
|
// Individual time column (hours, minutes, period)
|
|
103
135
|
timeColumn: {
|
|
104
136
|
alignItems: 'center',
|
|
105
137
|
gap: 2,
|
|
138
|
+
_web: {
|
|
139
|
+
display: 'flex',
|
|
140
|
+
},
|
|
106
141
|
},
|
|
107
142
|
|
|
108
143
|
// Time separator (colon)
|
|
@@ -114,6 +149,9 @@ export const datePickerStyles = StyleSheet.create((theme: Theme) => {
|
|
|
114
149
|
inputRow: {
|
|
115
150
|
flexDirection: 'row',
|
|
116
151
|
gap: 8,
|
|
152
|
+
_web: {
|
|
153
|
+
display: 'flex',
|
|
154
|
+
},
|
|
117
155
|
},
|
|
118
156
|
|
|
119
157
|
// Popover content wrapper
|
|
@@ -123,5 +161,94 @@ export const datePickerStyles = StyleSheet.create((theme: Theme) => {
|
|
|
123
161
|
...theme.shadows.lg,
|
|
124
162
|
overflow: 'hidden',
|
|
125
163
|
},
|
|
164
|
+
|
|
165
|
+
// Input container for DateInput/TimeInput
|
|
166
|
+
inputContainer: {
|
|
167
|
+
flexDirection: 'row',
|
|
168
|
+
alignItems: 'center',
|
|
169
|
+
borderWidth: 1,
|
|
170
|
+
borderRadius: 6,
|
|
171
|
+
overflow: 'hidden',
|
|
172
|
+
borderColor: theme.colors.border.primary,
|
|
173
|
+
backgroundColor: theme.colors.surface.primary,
|
|
174
|
+
variants: {
|
|
175
|
+
disabled: {
|
|
176
|
+
true: {
|
|
177
|
+
backgroundColor: theme.colors.surface.secondary,
|
|
178
|
+
},
|
|
179
|
+
false: {},
|
|
180
|
+
},
|
|
181
|
+
error: {
|
|
182
|
+
true: {
|
|
183
|
+
borderColor: theme.intents.error.primary,
|
|
184
|
+
},
|
|
185
|
+
false: {},
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
_web: {
|
|
189
|
+
display: 'flex',
|
|
190
|
+
flexDirection: 'row',
|
|
191
|
+
alignItems: 'center',
|
|
192
|
+
border: `1px solid ${theme.colors.border.primary}`,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
// Text input inside the input container
|
|
197
|
+
textInput: {
|
|
198
|
+
flex: 1,
|
|
199
|
+
padding: 8,
|
|
200
|
+
paddingHorizontal: 12,
|
|
201
|
+
fontSize: 14,
|
|
202
|
+
backgroundColor: 'transparent',
|
|
203
|
+
color: theme.colors.text.primary,
|
|
204
|
+
variants: {
|
|
205
|
+
disabled: {
|
|
206
|
+
true: {
|
|
207
|
+
color: theme.colors.text.tertiary,
|
|
208
|
+
},
|
|
209
|
+
false: {},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
_web: {
|
|
213
|
+
outline: 'none',
|
|
214
|
+
border: 'none',
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
// Error text below input
|
|
219
|
+
errorText: {
|
|
220
|
+
marginTop: 4,
|
|
221
|
+
color: theme.intents.error.primary,
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
// Label text above input
|
|
225
|
+
labelText: {
|
|
226
|
+
marginBottom: 4,
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
// Modal backdrop
|
|
230
|
+
modalBackdrop: {
|
|
231
|
+
flex: 1,
|
|
232
|
+
justifyContent: 'center',
|
|
233
|
+
alignItems: 'center',
|
|
234
|
+
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
// Selected day styling
|
|
238
|
+
selectedDay: {
|
|
239
|
+
backgroundColor: theme.intents.primary.primary,
|
|
240
|
+
borderRadius: 14,
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
selectedDayText: {
|
|
244
|
+
color: theme.intents.primary.contrast,
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
// Today styling
|
|
248
|
+
todayDay: {
|
|
249
|
+
borderWidth: 1,
|
|
250
|
+
borderColor: theme.intents.primary.primary,
|
|
251
|
+
borderRadius: 14,
|
|
252
|
+
},
|
|
126
253
|
} as const;
|
|
127
254
|
});
|