@idealyst/datepicker 1.2.35 → 1.2.37
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.web.tsx +34 -12
- package/src/DatePicker.styles.ts +83 -23
- package/src/DatePicker.web.tsx +92 -65
- package/src/DateTimePicker.web.tsx +18 -4
- package/src/IconSvg.web.tsx +4 -1
- package/src/InputStyles.ts +77 -21
- package/src/TimeInput.web.tsx +32 -12
- package/src/TimePicker.styles.ts +35 -8
- package/src/TimePicker.web.tsx +27 -11
- package/src/examples/DatePickerExamples.tsx +127 -4
- package/src/types.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/datepicker",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.37",
|
|
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.37",
|
|
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.37",
|
|
73
73
|
"@mdi/js": "^7.4.47",
|
|
74
74
|
"@mdi/react": "^1.6.1",
|
|
75
75
|
"@types/react": "^19.1.0",
|
package/src/DateInput.web.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useState, useRef, useEffect } from 'react';
|
|
2
2
|
import { getWebProps } from 'react-native-unistyles/web';
|
|
3
|
+
import { useUnistyles } from 'react-native-unistyles';
|
|
3
4
|
import { mdiCalendar } from '@mdi/js';
|
|
4
5
|
import { PositionedPortal } from '@idealyst/components/internal';
|
|
5
6
|
import { IconSvg } from './IconSvg.web';
|
|
@@ -16,6 +17,7 @@ export const DateInput: React.FC<DateInputProps> = ({
|
|
|
16
17
|
maxDate,
|
|
17
18
|
disabled = false,
|
|
18
19
|
error,
|
|
20
|
+
size = 'md',
|
|
19
21
|
style,
|
|
20
22
|
}) => {
|
|
21
23
|
const [open, setOpen] = useState(false);
|
|
@@ -24,14 +26,26 @@ export const DateInput: React.FC<DateInputProps> = ({
|
|
|
24
26
|
|
|
25
27
|
const styles = dateTimeInputStyles;
|
|
26
28
|
|
|
27
|
-
//
|
|
29
|
+
// Apply variants for disabled, error, and size states
|
|
30
|
+
styles.useVariants({
|
|
31
|
+
disabled,
|
|
32
|
+
error: !!error,
|
|
33
|
+
size,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Get theme for icon size and color
|
|
37
|
+
const { theme } = useUnistyles();
|
|
38
|
+
const iconSize = theme.sizes.input[size].iconSize;
|
|
39
|
+
const iconColor = theme.colors.text.secondary;
|
|
40
|
+
|
|
41
|
+
// Get dynamic styles with size variant
|
|
28
42
|
const labelTextStyle = (styles.labelText as any)({});
|
|
29
|
-
const inputContainerStyle = (styles.inputContainer as any)({ disabled, error: !!error });
|
|
30
|
-
const textInputStyle = (styles.textInput as any)({ disabled });
|
|
31
|
-
const iconButtonStyle = (styles.iconButton as any)({ disabled });
|
|
43
|
+
const inputContainerStyle = (styles.inputContainer as any)({ disabled, error: !!error, size });
|
|
44
|
+
const textInputStyle = (styles.textInput as any)({ disabled, size });
|
|
45
|
+
const iconButtonStyle = (styles.iconButton as any)({ disabled, size });
|
|
46
|
+
const iconStyle = (styles.icon as any)({ size });
|
|
32
47
|
const errorTextStyle = (styles.errorText as any)({});
|
|
33
48
|
const popoverContentStyle = (styles.popoverContent as any)({});
|
|
34
|
-
const iconColorStyle = (styles.iconColor as any)({ disabled });
|
|
35
49
|
|
|
36
50
|
// Format date to string
|
|
37
51
|
const formatDate = (date: Date | undefined): string => {
|
|
@@ -80,17 +94,24 @@ export const DateInput: React.FC<DateInputProps> = ({
|
|
|
80
94
|
};
|
|
81
95
|
|
|
82
96
|
const handleDateSelect = (date: Date) => {
|
|
97
|
+
console.log('[DateInput] handleDateSelect received:', date.toISOString());
|
|
98
|
+
console.log('[DateInput] Calling onChange with:', date.toISOString());
|
|
83
99
|
onChange(date);
|
|
84
100
|
setOpen(false);
|
|
85
101
|
};
|
|
86
102
|
|
|
87
|
-
// Get web props
|
|
103
|
+
// Get web props for all elements
|
|
88
104
|
const containerProps = getWebProps([inputContainerStyle]);
|
|
105
|
+
const labelProps = getWebProps([labelTextStyle]);
|
|
106
|
+
const inputProps = getWebProps([textInputStyle]);
|
|
107
|
+
const iconButtonProps = getWebProps([iconButtonStyle]);
|
|
108
|
+
const errorProps = getWebProps([errorTextStyle]);
|
|
109
|
+
const popoverProps = getWebProps([popoverContentStyle]);
|
|
89
110
|
|
|
90
111
|
return (
|
|
91
112
|
<div style={style as React.CSSProperties}>
|
|
92
113
|
{label && (
|
|
93
|
-
<span
|
|
114
|
+
<span {...labelProps}>{label}</span>
|
|
94
115
|
)}
|
|
95
116
|
<div {...containerProps} ref={triggerRef}>
|
|
96
117
|
<input
|
|
@@ -100,18 +121,19 @@ export const DateInput: React.FC<DateInputProps> = ({
|
|
|
100
121
|
onBlur={handleInputBlur}
|
|
101
122
|
placeholder={placeholder}
|
|
102
123
|
disabled={disabled}
|
|
103
|
-
|
|
124
|
+
{...inputProps}
|
|
104
125
|
/>
|
|
105
126
|
<button
|
|
106
|
-
|
|
127
|
+
type="button"
|
|
128
|
+
{...iconButtonProps}
|
|
107
129
|
onClick={() => !disabled && setOpen(!open)}
|
|
108
130
|
disabled={disabled}
|
|
109
131
|
>
|
|
110
|
-
<IconSvg path={mdiCalendar} size={
|
|
132
|
+
<IconSvg path={mdiCalendar} size={iconSize} color={iconColor} />
|
|
111
133
|
</button>
|
|
112
134
|
</div>
|
|
113
135
|
{error && (
|
|
114
|
-
<span
|
|
136
|
+
<span {...errorProps}>{error}</span>
|
|
115
137
|
)}
|
|
116
138
|
|
|
117
139
|
<PositionedPortal
|
|
@@ -123,7 +145,7 @@ export const DateInput: React.FC<DateInputProps> = ({
|
|
|
123
145
|
onEscapeKey={() => setOpen(false)}
|
|
124
146
|
zIndex={9999}
|
|
125
147
|
>
|
|
126
|
-
<div
|
|
148
|
+
<div {...popoverProps}>
|
|
127
149
|
<DatePicker
|
|
128
150
|
value={value ?? undefined}
|
|
129
151
|
onChange={handleDateSelect}
|
package/src/DatePicker.styles.ts
CHANGED
|
@@ -20,12 +20,17 @@ export type DatePickerDynamicProps = {
|
|
|
20
20
|
*/
|
|
21
21
|
export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme: Theme) => ({
|
|
22
22
|
// Calendar container - compact
|
|
23
|
-
calendar: (
|
|
23
|
+
calendar: (_props: DatePickerDynamicProps) => ({
|
|
24
24
|
padding: 8,
|
|
25
25
|
backgroundColor: theme.colors.surface.primary,
|
|
26
26
|
borderRadius: 6,
|
|
27
27
|
width: 220,
|
|
28
|
-
|
|
28
|
+
variants: {
|
|
29
|
+
disabled: {
|
|
30
|
+
true: { opacity: 0.6 },
|
|
31
|
+
false: { opacity: 1 },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
29
34
|
}),
|
|
30
35
|
|
|
31
36
|
// Calendar header with month/year and navigation
|
|
@@ -111,27 +116,44 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
|
|
|
111
116
|
}),
|
|
112
117
|
|
|
113
118
|
// Navigation button
|
|
114
|
-
navButton: (
|
|
119
|
+
navButton: (_props: DatePickerDynamicProps) => ({
|
|
115
120
|
width: 28,
|
|
116
121
|
height: 28,
|
|
117
122
|
alignItems: 'center' as const,
|
|
118
123
|
justifyContent: 'center' as const,
|
|
119
124
|
borderRadius: 4,
|
|
120
|
-
|
|
125
|
+
backgroundColor: 'transparent',
|
|
126
|
+
borderWidth: 0,
|
|
121
127
|
_web: {
|
|
122
128
|
display: 'flex',
|
|
123
|
-
|
|
129
|
+
background: 'none',
|
|
130
|
+
border: 'none',
|
|
131
|
+
padding: 0,
|
|
132
|
+
},
|
|
133
|
+
variants: {
|
|
134
|
+
disabled: {
|
|
135
|
+
true: { opacity: 0.4, _web: { cursor: 'not-allowed' } },
|
|
136
|
+
false: { opacity: 1, _web: { cursor: 'pointer' } },
|
|
137
|
+
},
|
|
124
138
|
},
|
|
125
139
|
}),
|
|
126
140
|
|
|
127
141
|
// Title button (month/year selector)
|
|
128
|
-
titleButton: (
|
|
142
|
+
titleButton: (_props: DatePickerDynamicProps) => ({
|
|
129
143
|
paddingHorizontal: 8,
|
|
130
144
|
paddingVertical: 4,
|
|
131
145
|
borderRadius: 4,
|
|
132
|
-
|
|
146
|
+
backgroundColor: 'transparent',
|
|
147
|
+
borderWidth: 0,
|
|
133
148
|
_web: {
|
|
134
|
-
|
|
149
|
+
background: 'none',
|
|
150
|
+
border: 'none',
|
|
151
|
+
},
|
|
152
|
+
variants: {
|
|
153
|
+
disabled: {
|
|
154
|
+
true: { opacity: 0.6, _web: { cursor: 'not-allowed' } },
|
|
155
|
+
false: { opacity: 1, _web: { cursor: 'pointer' } },
|
|
156
|
+
},
|
|
135
157
|
},
|
|
136
158
|
}),
|
|
137
159
|
|
|
@@ -141,23 +163,36 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
|
|
|
141
163
|
color: theme.colors.text.primary,
|
|
142
164
|
}),
|
|
143
165
|
|
|
144
|
-
// Day button
|
|
145
|
-
dayButton: (
|
|
146
|
-
width:
|
|
147
|
-
height:
|
|
166
|
+
// Day button - fills entire cell for better click handling
|
|
167
|
+
dayButton: (_props: DatePickerDynamicProps) => ({
|
|
168
|
+
width: 28,
|
|
169
|
+
height: 28,
|
|
148
170
|
alignItems: 'center' as const,
|
|
149
171
|
justifyContent: 'center' as const,
|
|
150
|
-
borderRadius:
|
|
151
|
-
|
|
172
|
+
borderRadius: 14,
|
|
173
|
+
backgroundColor: 'transparent',
|
|
174
|
+
borderWidth: 0,
|
|
152
175
|
_web: {
|
|
153
176
|
display: 'flex',
|
|
154
|
-
|
|
177
|
+
background: 'none',
|
|
178
|
+
border: 'none',
|
|
179
|
+
padding: 0,
|
|
180
|
+
cursor: 'pointer',
|
|
181
|
+
},
|
|
182
|
+
variants: {
|
|
183
|
+
disabled: {
|
|
184
|
+
true: { opacity: 0.4, _web: { cursor: 'not-allowed' } },
|
|
185
|
+
false: { opacity: 1, _web: { cursor: 'pointer' } },
|
|
186
|
+
},
|
|
155
187
|
},
|
|
156
188
|
}),
|
|
157
189
|
|
|
158
190
|
dayText: (_props: DatePickerDynamicProps) => ({
|
|
159
191
|
fontSize: 12,
|
|
160
192
|
color: theme.colors.text.primary,
|
|
193
|
+
_web: {
|
|
194
|
+
pointerEvents: 'none',
|
|
195
|
+
},
|
|
161
196
|
}),
|
|
162
197
|
|
|
163
198
|
weekdayText: (_props: DatePickerDynamicProps) => ({
|
|
@@ -166,7 +201,7 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
|
|
|
166
201
|
}),
|
|
167
202
|
|
|
168
203
|
// Month/Year selector item
|
|
169
|
-
selectorItem: (
|
|
204
|
+
selectorItem: (_props: DatePickerDynamicProps) => ({
|
|
170
205
|
minWidth: 48,
|
|
171
206
|
paddingVertical: 6,
|
|
172
207
|
paddingHorizontal: 8,
|
|
@@ -174,41 +209,66 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
|
|
|
174
209
|
alignItems: 'center' as const,
|
|
175
210
|
justifyContent: 'center' as const,
|
|
176
211
|
borderRadius: 4,
|
|
177
|
-
|
|
212
|
+
backgroundColor: 'transparent',
|
|
213
|
+
borderWidth: 0,
|
|
178
214
|
_web: {
|
|
179
215
|
display: 'flex',
|
|
180
|
-
|
|
216
|
+
background: 'none',
|
|
217
|
+
border: 'none',
|
|
218
|
+
},
|
|
219
|
+
variants: {
|
|
220
|
+
disabled: {
|
|
221
|
+
true: { opacity: 0.4, _web: { cursor: 'not-allowed' } },
|
|
222
|
+
false: { opacity: 1, _web: { cursor: 'pointer' } },
|
|
223
|
+
},
|
|
181
224
|
},
|
|
182
225
|
}),
|
|
183
226
|
|
|
184
227
|
selectorItemSelected: (_props: DatePickerDynamicProps) => ({
|
|
185
228
|
backgroundColor: theme.intents.primary.primary,
|
|
229
|
+
_web: {
|
|
230
|
+
background: theme.intents.primary.primary,
|
|
231
|
+
},
|
|
186
232
|
}),
|
|
187
233
|
|
|
188
234
|
selectorItemText: (_props: DatePickerDynamicProps) => ({
|
|
189
235
|
fontSize: 12,
|
|
190
236
|
color: theme.colors.text.primary,
|
|
237
|
+
_web: {
|
|
238
|
+
pointerEvents: 'none',
|
|
239
|
+
},
|
|
191
240
|
}),
|
|
192
241
|
|
|
193
242
|
selectorItemTextSelected: (_props: DatePickerDynamicProps) => ({
|
|
194
243
|
color: theme.intents.primary.contrast,
|
|
244
|
+
_web: {
|
|
245
|
+
pointerEvents: 'none',
|
|
246
|
+
},
|
|
195
247
|
}),
|
|
196
248
|
|
|
197
249
|
// Selected day styling
|
|
198
250
|
selectedDay: (_props: DatePickerDynamicProps) => ({
|
|
199
251
|
backgroundColor: theme.intents.primary.primary,
|
|
200
|
-
borderRadius:
|
|
252
|
+
borderRadius: 6,
|
|
253
|
+
_web: {
|
|
254
|
+
background: theme.intents.primary.primary,
|
|
255
|
+
},
|
|
201
256
|
}),
|
|
202
257
|
|
|
203
258
|
selectedDayText: (_props: DatePickerDynamicProps) => ({
|
|
204
259
|
color: theme.intents.primary.contrast,
|
|
260
|
+
_web: {
|
|
261
|
+
pointerEvents: 'none',
|
|
262
|
+
},
|
|
205
263
|
}),
|
|
206
264
|
|
|
207
|
-
// Today styling
|
|
265
|
+
// Today styling - subtle background highlight
|
|
208
266
|
todayDay: (_props: DatePickerDynamicProps) => ({
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
267
|
+
backgroundColor: theme.intents.primary.muted,
|
|
268
|
+
borderRadius: 6,
|
|
269
|
+
_web: {
|
|
270
|
+
background: theme.intents.primary.muted,
|
|
271
|
+
},
|
|
212
272
|
}),
|
|
213
273
|
|
|
214
274
|
// Icon color helper
|
package/src/DatePicker.web.tsx
CHANGED
|
@@ -23,6 +23,11 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
23
23
|
|
|
24
24
|
const styles = datePickerCalendarStyles;
|
|
25
25
|
|
|
26
|
+
// Apply variants for disabled state
|
|
27
|
+
styles.useVariants({
|
|
28
|
+
disabled,
|
|
29
|
+
});
|
|
30
|
+
|
|
26
31
|
const { days, monthShort, year } = useMemo(() => {
|
|
27
32
|
const year = currentMonth.getFullYear();
|
|
28
33
|
const month = currentMonth.getMonth();
|
|
@@ -78,8 +83,16 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
78
83
|
|
|
79
84
|
const isDisabled = (date: Date): boolean => {
|
|
80
85
|
if (disabled) return true;
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
// Normalize the date to start of day for comparison
|
|
87
|
+
const normalizedDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
88
|
+
if (minDate) {
|
|
89
|
+
const min = new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate());
|
|
90
|
+
if (normalizedDate < min) return true;
|
|
91
|
+
}
|
|
92
|
+
if (maxDate) {
|
|
93
|
+
const max = new Date(maxDate.getFullYear(), maxDate.getMonth(), maxDate.getDate());
|
|
94
|
+
if (normalizedDate > max) return true;
|
|
95
|
+
}
|
|
83
96
|
return false;
|
|
84
97
|
};
|
|
85
98
|
|
|
@@ -92,8 +105,13 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
92
105
|
};
|
|
93
106
|
|
|
94
107
|
const handleDayPress = (date: Date) => {
|
|
108
|
+
console.log('[DatePicker] handleDayPress called with:', date.toISOString());
|
|
109
|
+
console.log('[DatePicker] isDisabled:', isDisabled(date));
|
|
95
110
|
if (!isDisabled(date)) {
|
|
96
|
-
|
|
111
|
+
// Create a new date to avoid mutating the calendar's date objects
|
|
112
|
+
const newDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
113
|
+
console.log('[DatePicker] Calling onChange with:', newDate.toISOString());
|
|
114
|
+
onChange(newDate);
|
|
97
115
|
}
|
|
98
116
|
};
|
|
99
117
|
|
|
@@ -140,13 +158,22 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
140
158
|
const selectorItemTextSelectedStyle = (styles.selectorItemTextSelected as any)({});
|
|
141
159
|
const iconColorStyle = (styles.iconColor as any)({});
|
|
142
160
|
|
|
143
|
-
// Get web props
|
|
161
|
+
// Get web props for all elements
|
|
144
162
|
const calendarProps = getWebProps([calendarStyle, style as any]);
|
|
145
163
|
const headerProps = getWebProps([headerStyle]);
|
|
146
164
|
const weekdayRowProps = getWebProps([weekdayRowStyle]);
|
|
165
|
+
const weekdayCellProps = getWebProps([weekdayCellStyle]);
|
|
147
166
|
const gridProps = getWebProps([gridStyle]);
|
|
148
167
|
const monthGridProps = getWebProps([monthGridStyle]);
|
|
149
168
|
const yearGridProps = getWebProps([yearGridStyle]);
|
|
169
|
+
const navButtonProps = getWebProps([navButtonStyle]);
|
|
170
|
+
const titleButtonProps = getWebProps([titleButtonStyle]);
|
|
171
|
+
const titleTextProps = getWebProps([titleTextStyle]);
|
|
172
|
+
const weekdayTextProps = getWebProps([weekdayTextStyle]);
|
|
173
|
+
const selectorItemProps = getWebProps([selectorItemStyle]);
|
|
174
|
+
const selectorItemSelectedProps = getWebProps([selectorItemStyle, selectorItemSelectedStyle]);
|
|
175
|
+
const selectorItemTextProps = getWebProps([selectorItemTextStyle]);
|
|
176
|
+
const selectorItemTextSelectedProps = getWebProps([selectorItemTextStyle, selectorItemTextSelectedStyle]);
|
|
150
177
|
|
|
151
178
|
// Render month selector
|
|
152
179
|
if (viewMode === 'months') {
|
|
@@ -154,18 +181,20 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
154
181
|
<div {...calendarProps}>
|
|
155
182
|
<div {...headerProps}>
|
|
156
183
|
<button
|
|
157
|
-
|
|
184
|
+
type="button"
|
|
185
|
+
{...navButtonProps}
|
|
158
186
|
onClick={() => setViewMode('calendar')}
|
|
159
187
|
disabled={disabled}
|
|
160
188
|
>
|
|
161
189
|
<IconSvg path={mdiChevronLeft} size={16} color={iconColorStyle.color} />
|
|
162
190
|
</button>
|
|
163
191
|
<button
|
|
164
|
-
|
|
192
|
+
type="button"
|
|
193
|
+
{...titleButtonProps}
|
|
165
194
|
onClick={() => setViewMode('years')}
|
|
166
195
|
disabled={disabled}
|
|
167
196
|
>
|
|
168
|
-
<span
|
|
197
|
+
<span {...titleTextProps}>{year}</span>
|
|
169
198
|
</button>
|
|
170
199
|
<div style={{ width: 28 }} />
|
|
171
200
|
</div>
|
|
@@ -174,20 +203,13 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
174
203
|
const isCurrentMonth = index === currentMonth.getMonth();
|
|
175
204
|
return (
|
|
176
205
|
<button
|
|
206
|
+
type="button"
|
|
177
207
|
key={month}
|
|
178
|
-
|
|
179
|
-
...selectorItemStyle,
|
|
180
|
-
...(isCurrentMonth ? selectorItemSelectedStyle : {}),
|
|
181
|
-
}}
|
|
208
|
+
{...(isCurrentMonth ? selectorItemSelectedProps : selectorItemProps)}
|
|
182
209
|
onClick={() => handleMonthSelect(index)}
|
|
183
210
|
disabled={disabled}
|
|
184
211
|
>
|
|
185
|
-
<span
|
|
186
|
-
style={{
|
|
187
|
-
...selectorItemTextStyle,
|
|
188
|
-
...(isCurrentMonth ? selectorItemTextSelectedStyle : {}),
|
|
189
|
-
}}
|
|
190
|
-
>
|
|
212
|
+
<span {...(isCurrentMonth ? selectorItemTextSelectedProps : selectorItemTextProps)}>
|
|
191
213
|
{month}
|
|
192
214
|
</span>
|
|
193
215
|
</button>
|
|
@@ -204,17 +226,19 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
204
226
|
<div {...calendarProps}>
|
|
205
227
|
<div {...headerProps}>
|
|
206
228
|
<button
|
|
207
|
-
|
|
229
|
+
type="button"
|
|
230
|
+
{...navButtonProps}
|
|
208
231
|
onClick={goToPrevYearRange}
|
|
209
232
|
disabled={disabled}
|
|
210
233
|
>
|
|
211
234
|
<IconSvg path={mdiChevronLeft} size={16} color={iconColorStyle.color} />
|
|
212
235
|
</button>
|
|
213
|
-
<span
|
|
236
|
+
<span {...titleTextProps}>
|
|
214
237
|
{yearRange[0]} - {yearRange[yearRange.length - 1]}
|
|
215
238
|
</span>
|
|
216
239
|
<button
|
|
217
|
-
|
|
240
|
+
type="button"
|
|
241
|
+
{...navButtonProps}
|
|
218
242
|
onClick={goToNextYearRange}
|
|
219
243
|
disabled={disabled}
|
|
220
244
|
>
|
|
@@ -226,20 +250,13 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
226
250
|
const isCurrentYear = yr === currentMonth.getFullYear();
|
|
227
251
|
return (
|
|
228
252
|
<button
|
|
253
|
+
type="button"
|
|
229
254
|
key={yr}
|
|
230
|
-
|
|
231
|
-
...selectorItemStyle,
|
|
232
|
-
...(isCurrentYear ? selectorItemSelectedStyle : {}),
|
|
233
|
-
}}
|
|
255
|
+
{...(isCurrentYear ? selectorItemSelectedProps : selectorItemProps)}
|
|
234
256
|
onClick={() => handleYearSelect(yr)}
|
|
235
257
|
disabled={disabled}
|
|
236
258
|
>
|
|
237
|
-
<span
|
|
238
|
-
style={{
|
|
239
|
-
...selectorItemTextStyle,
|
|
240
|
-
...(isCurrentYear ? selectorItemTextSelectedStyle : {}),
|
|
241
|
-
}}
|
|
242
|
-
>
|
|
259
|
+
<span {...(isCurrentYear ? selectorItemTextSelectedProps : selectorItemTextProps)}>
|
|
243
260
|
{yr}
|
|
244
261
|
</span>
|
|
245
262
|
</button>
|
|
@@ -250,29 +267,49 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
250
267
|
);
|
|
251
268
|
}
|
|
252
269
|
|
|
270
|
+
// Get day cell props
|
|
271
|
+
const dayCellStyle = (styles.dayCell as any)({});
|
|
272
|
+
const selectedDayStyle = (styles.selectedDay as any)({});
|
|
273
|
+
const todayDayStyle = (styles.todayDay as any)({});
|
|
274
|
+
const dayButtonStyle = (styles.dayButton as any)({ disabled: false });
|
|
275
|
+
const dayButtonDisabledStyle = (styles.dayButton as any)({ disabled: true });
|
|
276
|
+
const dayTextStyle = (styles.dayText as any)({});
|
|
277
|
+
const selectedDayTextStyle = (styles.selectedDayText as any)({});
|
|
278
|
+
|
|
279
|
+
const dayCellProps = getWebProps([dayCellStyle]);
|
|
280
|
+
const dayButtonProps = getWebProps([dayButtonStyle]);
|
|
281
|
+
const dayButtonSelectedProps = getWebProps([dayButtonStyle, selectedDayStyle]);
|
|
282
|
+
const dayButtonTodayProps = getWebProps([dayButtonStyle, todayDayStyle]);
|
|
283
|
+
const dayButtonDisabledProps = getWebProps([dayButtonDisabledStyle]);
|
|
284
|
+
const dayTextProps = getWebProps([dayTextStyle]);
|
|
285
|
+
const selectedDayTextProps = getWebProps([dayTextStyle, selectedDayTextStyle]);
|
|
286
|
+
|
|
253
287
|
// Render calendar (default)
|
|
254
288
|
return (
|
|
255
289
|
<div {...calendarProps}>
|
|
256
290
|
{/* Header */}
|
|
257
291
|
<div {...headerProps}>
|
|
258
292
|
<button
|
|
259
|
-
|
|
293
|
+
type="button"
|
|
294
|
+
{...navButtonProps}
|
|
260
295
|
onClick={goToPrevMonth}
|
|
261
296
|
disabled={disabled}
|
|
262
297
|
>
|
|
263
298
|
<IconSvg path={mdiChevronLeft} size={16} color={iconColorStyle.color} />
|
|
264
299
|
</button>
|
|
265
300
|
<button
|
|
266
|
-
|
|
301
|
+
type="button"
|
|
302
|
+
{...titleButtonProps}
|
|
267
303
|
onClick={() => setViewMode('months')}
|
|
268
304
|
disabled={disabled}
|
|
269
305
|
>
|
|
270
|
-
<span
|
|
306
|
+
<span {...titleTextProps}>
|
|
271
307
|
{monthShort} {year}
|
|
272
308
|
</span>
|
|
273
309
|
</button>
|
|
274
310
|
<button
|
|
275
|
-
|
|
311
|
+
type="button"
|
|
312
|
+
{...navButtonProps}
|
|
276
313
|
onClick={goToNextMonth}
|
|
277
314
|
disabled={disabled}
|
|
278
315
|
>
|
|
@@ -283,51 +320,41 @@ export const DatePicker: React.FC<DatePickerProps> = ({
|
|
|
283
320
|
{/* Weekday headers */}
|
|
284
321
|
<div {...weekdayRowProps}>
|
|
285
322
|
{WEEKDAYS.map((day, i) => (
|
|
286
|
-
<div key={i}
|
|
287
|
-
<span
|
|
323
|
+
<div key={i} {...weekdayCellProps}>
|
|
324
|
+
<span {...weekdayTextProps}>{day}</span>
|
|
288
325
|
</div>
|
|
289
326
|
))}
|
|
290
327
|
</div>
|
|
291
328
|
|
|
292
329
|
{/* Calendar grid */}
|
|
293
330
|
<div {...gridProps}>
|
|
294
|
-
{days.map(({ date, isCurrentMonth }, index) => {
|
|
331
|
+
{days.map(({ date, isCurrentMonth: isCurrentMonthDay }, index) => {
|
|
295
332
|
const selected = isSelected(date);
|
|
296
333
|
const today = isToday(date);
|
|
297
334
|
const dayDisabled = isDisabled(date);
|
|
298
335
|
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
336
|
+
// Get appropriate button props (className and ref only)
|
|
337
|
+
const buttonProps = dayDisabled
|
|
338
|
+
? dayButtonDisabledProps
|
|
339
|
+
: selected
|
|
340
|
+
? dayButtonSelectedProps
|
|
341
|
+
: today
|
|
342
|
+
? dayButtonTodayProps
|
|
343
|
+
: dayButtonProps;
|
|
305
344
|
|
|
306
345
|
return (
|
|
307
|
-
<
|
|
346
|
+
<button
|
|
308
347
|
key={index}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}}
|
|
348
|
+
type="button"
|
|
349
|
+
className={buttonProps.className}
|
|
350
|
+
style={{ opacity: (!isCurrentMonthDay || dayDisabled) ? 0.3 : 1 }}
|
|
351
|
+
onClick={() => handleDayPress(date)}
|
|
352
|
+
disabled={dayDisabled}
|
|
315
353
|
>
|
|
316
|
-
<
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
>
|
|
321
|
-
<span
|
|
322
|
-
style={{
|
|
323
|
-
...dayTextStyle,
|
|
324
|
-
...(selected ? selectedDayTextStyle : {}),
|
|
325
|
-
}}
|
|
326
|
-
>
|
|
327
|
-
{date.getDate()}
|
|
328
|
-
</span>
|
|
329
|
-
</button>
|
|
330
|
-
</div>
|
|
354
|
+
<span {...(selected ? selectedDayTextProps : dayTextProps)}>
|
|
355
|
+
{date.getDate()}
|
|
356
|
+
</span>
|
|
357
|
+
</button>
|
|
331
358
|
);
|
|
332
359
|
})}
|
|
333
360
|
</div>
|
|
@@ -15,6 +15,7 @@ export const DateTimePicker: React.FC<DateTimePickerProps> = ({
|
|
|
15
15
|
minuteStep = 1,
|
|
16
16
|
disabled = false,
|
|
17
17
|
error,
|
|
18
|
+
size = 'md',
|
|
18
19
|
style,
|
|
19
20
|
}) => {
|
|
20
21
|
const styles = dateTimePickerStyles;
|
|
@@ -25,6 +26,8 @@ export const DateTimePicker: React.FC<DateTimePickerProps> = ({
|
|
|
25
26
|
const inputColumnStyle = (styles.inputColumn as any)({});
|
|
26
27
|
|
|
27
28
|
const handleDateChange = (date: Date | null) => {
|
|
29
|
+
console.log('[DateTimePicker] handleDateChange received:', date?.toISOString());
|
|
30
|
+
console.log('[DateTimePicker] Current value:', value?.toISOString());
|
|
28
31
|
if (!date) {
|
|
29
32
|
onChange(null);
|
|
30
33
|
return;
|
|
@@ -32,12 +35,18 @@ export const DateTimePicker: React.FC<DateTimePickerProps> = ({
|
|
|
32
35
|
// Preserve time from current value, or use noon as default
|
|
33
36
|
const hours = value?.getHours() ?? 12;
|
|
34
37
|
const minutes = value?.getMinutes() ?? 0;
|
|
38
|
+
console.log('[DateTimePicker] Preserving hours:', hours, 'minutes:', minutes);
|
|
35
39
|
const updated = new Date(date);
|
|
40
|
+
console.log('[DateTimePicker] After new Date(date):', updated.toISOString());
|
|
36
41
|
updated.setHours(hours, minutes, 0, 0);
|
|
42
|
+
console.log('[DateTimePicker] After setHours:', updated.toISOString());
|
|
43
|
+
console.log('[DateTimePicker] Calling onChange with:', updated.toISOString());
|
|
37
44
|
onChange(updated);
|
|
38
45
|
};
|
|
39
46
|
|
|
40
47
|
const handleTimeChange = (time: Date | null) => {
|
|
48
|
+
console.log('[DateTimePicker] handleTimeChange received:', time?.toISOString());
|
|
49
|
+
console.log('[DateTimePicker] Current value:', value?.toISOString());
|
|
41
50
|
if (!time) {
|
|
42
51
|
// Only clear time component, keep date if it exists
|
|
43
52
|
if (value) {
|
|
@@ -58,19 +67,22 @@ export const DateTimePicker: React.FC<DateTimePickerProps> = ({
|
|
|
58
67
|
0,
|
|
59
68
|
0
|
|
60
69
|
);
|
|
70
|
+
console.log('[DateTimePicker] Time change - calling onChange with:', updated.toISOString());
|
|
61
71
|
onChange(updated);
|
|
62
72
|
};
|
|
63
73
|
|
|
64
|
-
// Get web props
|
|
74
|
+
// Get web props for all elements
|
|
65
75
|
const inputRowProps = getWebProps([inputRowStyle]);
|
|
76
|
+
const labelProps = getWebProps([labelTextStyle]);
|
|
77
|
+
const inputColumnProps = getWebProps([inputColumnStyle]);
|
|
66
78
|
|
|
67
79
|
return (
|
|
68
80
|
<div style={style as React.CSSProperties}>
|
|
69
81
|
{label && (
|
|
70
|
-
<span
|
|
82
|
+
<span {...labelProps}>{label}</span>
|
|
71
83
|
)}
|
|
72
84
|
<div {...inputRowProps}>
|
|
73
|
-
<div
|
|
85
|
+
<div {...inputColumnProps}>
|
|
74
86
|
<DateInput
|
|
75
87
|
value={value ?? undefined}
|
|
76
88
|
onChange={handleDateChange}
|
|
@@ -79,9 +91,10 @@ export const DateTimePicker: React.FC<DateTimePickerProps> = ({
|
|
|
79
91
|
maxDate={maxDate}
|
|
80
92
|
disabled={disabled}
|
|
81
93
|
error={error}
|
|
94
|
+
size={size}
|
|
82
95
|
/>
|
|
83
96
|
</div>
|
|
84
|
-
<div
|
|
97
|
+
<div {...inputColumnProps}>
|
|
85
98
|
<TimeInput
|
|
86
99
|
value={value ?? undefined}
|
|
87
100
|
onChange={handleTimeChange}
|
|
@@ -89,6 +102,7 @@ export const DateTimePicker: React.FC<DateTimePickerProps> = ({
|
|
|
89
102
|
mode={timeMode}
|
|
90
103
|
minuteStep={minuteStep}
|
|
91
104
|
disabled={disabled}
|
|
105
|
+
size={size}
|
|
92
106
|
/>
|
|
93
107
|
</div>
|
|
94
108
|
</div>
|
package/src/IconSvg.web.tsx
CHANGED
|
@@ -22,11 +22,14 @@ export const IconSvg: React.FC<IconSvgProps> = ({
|
|
|
22
22
|
}) => {
|
|
23
23
|
if (!path) return null;
|
|
24
24
|
|
|
25
|
+
// Convert numeric sizes to pixel strings - @mdi/react interprets bare numbers as rem
|
|
26
|
+
const sizeValue = typeof size === 'number' ? `${size}px` : size;
|
|
27
|
+
|
|
25
28
|
return (
|
|
26
29
|
<MdiIcon
|
|
27
30
|
style={style}
|
|
28
31
|
path={path}
|
|
29
|
-
size={
|
|
32
|
+
size={sizeValue}
|
|
30
33
|
color={color}
|
|
31
34
|
aria-label={ariaLabel}
|
|
32
35
|
/>
|
package/src/InputStyles.ts
CHANGED
|
@@ -18,51 +18,103 @@ export type InputDynamicProps = {
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Shared input styles with theme reactivity.
|
|
21
|
+
* Uses $iterator pattern to support size variants (xs, sm, md, lg, xl).
|
|
21
22
|
*/
|
|
22
23
|
export const dateTimeInputStyles = defineStyle('DateTimeInput', (theme: Theme) => ({
|
|
23
24
|
// Input container for DateInput/TimeInput
|
|
24
|
-
inputContainer: (
|
|
25
|
+
inputContainer: (_props: InputDynamicProps) => ({
|
|
25
26
|
flexDirection: 'row' as const,
|
|
26
27
|
alignItems: 'center' as const,
|
|
27
28
|
borderWidth: 1,
|
|
28
|
-
|
|
29
|
+
borderStyle: 'solid' as const,
|
|
30
|
+
borderRadius: theme.radii.md,
|
|
29
31
|
overflow: 'hidden' as const,
|
|
30
|
-
borderColor:
|
|
31
|
-
backgroundColor:
|
|
32
|
+
borderColor: theme.colors.border.primary,
|
|
33
|
+
backgroundColor: theme.colors.surface.primary,
|
|
32
34
|
_web: {
|
|
33
35
|
display: 'flex',
|
|
34
36
|
flexDirection: 'row',
|
|
35
37
|
alignItems: 'center',
|
|
36
|
-
|
|
38
|
+
boxSizing: 'border-box',
|
|
39
|
+
},
|
|
40
|
+
variants: {
|
|
41
|
+
disabled: {
|
|
42
|
+
true: { backgroundColor: theme.colors.surface.secondary },
|
|
43
|
+
false: { backgroundColor: theme.colors.surface.primary },
|
|
44
|
+
},
|
|
45
|
+
error: {
|
|
46
|
+
true: { borderColor: theme.intents.danger.primary },
|
|
47
|
+
false: { borderColor: theme.colors.border.primary },
|
|
48
|
+
},
|
|
49
|
+
// $iterator expands for each input size (xs, sm, md, lg, xl)
|
|
50
|
+
size: {
|
|
51
|
+
height: theme.sizes.$input.height,
|
|
52
|
+
paddingHorizontal: theme.sizes.$input.paddingHorizontal,
|
|
53
|
+
},
|
|
37
54
|
},
|
|
38
55
|
}),
|
|
39
56
|
|
|
40
57
|
// Text input inside the input container
|
|
41
|
-
textInput: (
|
|
58
|
+
textInput: (_props: InputDynamicProps) => ({
|
|
42
59
|
flex: 1,
|
|
43
|
-
|
|
44
|
-
paddingHorizontal: 12,
|
|
45
|
-
fontSize: 14,
|
|
60
|
+
minWidth: 0,
|
|
46
61
|
backgroundColor: 'transparent',
|
|
47
|
-
color:
|
|
62
|
+
color: theme.colors.text.primary,
|
|
63
|
+
fontWeight: '400' as const,
|
|
48
64
|
_web: {
|
|
49
65
|
outline: 'none',
|
|
50
66
|
border: 'none',
|
|
67
|
+
fontFamily: 'inherit',
|
|
68
|
+
},
|
|
69
|
+
variants: {
|
|
70
|
+
disabled: {
|
|
71
|
+
true: { color: theme.colors.text.tertiary },
|
|
72
|
+
false: { color: theme.colors.text.primary },
|
|
73
|
+
},
|
|
74
|
+
// $iterator expands for each input size
|
|
75
|
+
size: {
|
|
76
|
+
fontSize: theme.sizes.$input.fontSize,
|
|
77
|
+
},
|
|
51
78
|
},
|
|
52
79
|
}),
|
|
53
80
|
|
|
54
81
|
// Icon button inside input
|
|
55
|
-
iconButton: (
|
|
56
|
-
width: 32,
|
|
57
|
-
height: 32,
|
|
82
|
+
iconButton: (_props: InputDynamicProps) => ({
|
|
58
83
|
alignItems: 'center' as const,
|
|
59
84
|
justifyContent: 'center' as const,
|
|
60
|
-
marginRight: 4,
|
|
61
85
|
borderRadius: 4,
|
|
62
|
-
|
|
86
|
+
backgroundColor: 'transparent',
|
|
87
|
+
borderWidth: 0,
|
|
88
|
+
flexShrink: 0,
|
|
63
89
|
_web: {
|
|
64
90
|
display: 'flex',
|
|
65
|
-
|
|
91
|
+
background: 'none',
|
|
92
|
+
border: 'none',
|
|
93
|
+
padding: 0,
|
|
94
|
+
},
|
|
95
|
+
variants: {
|
|
96
|
+
disabled: {
|
|
97
|
+
true: { opacity: 0.4, _web: { cursor: 'not-allowed' } },
|
|
98
|
+
false: { opacity: 1, _web: { cursor: 'pointer' } },
|
|
99
|
+
},
|
|
100
|
+
// $iterator expands for each input size
|
|
101
|
+
size: {
|
|
102
|
+
width: theme.sizes.$input.iconSize,
|
|
103
|
+
height: theme.sizes.$input.iconSize,
|
|
104
|
+
marginLeft: theme.sizes.$input.iconMargin,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
|
|
109
|
+
// Icon inside button - sized based on input size
|
|
110
|
+
icon: (_props: InputDynamicProps) => ({
|
|
111
|
+
color: theme.colors.text.secondary,
|
|
112
|
+
variants: {
|
|
113
|
+
// $iterator expands for each input size
|
|
114
|
+
size: {
|
|
115
|
+
width: theme.sizes.$input.iconSize,
|
|
116
|
+
height: theme.sizes.$input.iconSize,
|
|
117
|
+
},
|
|
66
118
|
},
|
|
67
119
|
}),
|
|
68
120
|
|
|
@@ -101,17 +153,21 @@ export const dateTimeInputStyles = defineStyle('DateTimeInput', (theme: Theme) =
|
|
|
101
153
|
}),
|
|
102
154
|
|
|
103
155
|
// Close button
|
|
104
|
-
closeButton: (
|
|
156
|
+
closeButton: (_props: InputDynamicProps) => ({
|
|
105
157
|
marginTop: 8,
|
|
106
158
|
paddingVertical: 8,
|
|
107
159
|
paddingHorizontal: 16,
|
|
108
160
|
alignItems: 'center' as const,
|
|
109
161
|
justifyContent: 'center' as const,
|
|
110
162
|
borderRadius: 4,
|
|
111
|
-
opacity: disabled ? 0.4 : 1,
|
|
112
163
|
_web: {
|
|
113
164
|
display: 'flex',
|
|
114
|
-
|
|
165
|
+
},
|
|
166
|
+
variants: {
|
|
167
|
+
disabled: {
|
|
168
|
+
true: { opacity: 0.4, _web: { cursor: 'not-allowed' } },
|
|
169
|
+
false: { opacity: 1, _web: { cursor: 'pointer' } },
|
|
170
|
+
},
|
|
115
171
|
},
|
|
116
172
|
}),
|
|
117
173
|
|
|
@@ -121,7 +177,7 @@ export const dateTimeInputStyles = defineStyle('DateTimeInput', (theme: Theme) =
|
|
|
121
177
|
}),
|
|
122
178
|
|
|
123
179
|
// Icon color helper
|
|
124
|
-
iconColor: (
|
|
125
|
-
color: theme.colors.text.
|
|
180
|
+
iconColor: (_props: InputDynamicProps) => ({
|
|
181
|
+
color: theme.colors.text.secondary,
|
|
126
182
|
}),
|
|
127
183
|
}));
|
package/src/TimeInput.web.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useState, useRef, useEffect } from 'react';
|
|
2
2
|
import { getWebProps } from 'react-native-unistyles/web';
|
|
3
|
+
import { useUnistyles } from 'react-native-unistyles';
|
|
3
4
|
import { mdiClockOutline } from '@mdi/js';
|
|
4
5
|
import { PositionedPortal } from '@idealyst/components/internal';
|
|
5
6
|
import { IconSvg } from './IconSvg.web';
|
|
@@ -16,6 +17,7 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
16
17
|
minuteStep = 1,
|
|
17
18
|
disabled = false,
|
|
18
19
|
error,
|
|
20
|
+
size = 'md',
|
|
19
21
|
style,
|
|
20
22
|
}) => {
|
|
21
23
|
const [open, setOpen] = useState(false);
|
|
@@ -24,14 +26,26 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
24
26
|
|
|
25
27
|
const styles = dateTimeInputStyles;
|
|
26
28
|
|
|
27
|
-
//
|
|
29
|
+
// Apply variants for disabled, error, and size states
|
|
30
|
+
styles.useVariants({
|
|
31
|
+
disabled,
|
|
32
|
+
error: !!error,
|
|
33
|
+
size,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Get theme for icon size and color
|
|
37
|
+
const { theme } = useUnistyles();
|
|
38
|
+
const iconSize = theme.sizes.input[size].iconSize;
|
|
39
|
+
const iconColor = theme.colors.text.secondary;
|
|
40
|
+
|
|
41
|
+
// Get dynamic styles with size variant
|
|
28
42
|
const labelTextStyle = (styles.labelText as any)({});
|
|
29
|
-
const inputContainerStyle = (styles.inputContainer as any)({ disabled, error: !!error });
|
|
30
|
-
const textInputStyle = (styles.textInput as any)({ disabled });
|
|
31
|
-
const iconButtonStyle = (styles.iconButton as any)({ disabled });
|
|
43
|
+
const inputContainerStyle = (styles.inputContainer as any)({ disabled, error: !!error, size });
|
|
44
|
+
const textInputStyle = (styles.textInput as any)({ disabled, size });
|
|
45
|
+
const iconButtonStyle = (styles.iconButton as any)({ disabled, size });
|
|
46
|
+
const iconStyle = (styles.icon as any)({ size });
|
|
32
47
|
const errorTextStyle = (styles.errorText as any)({});
|
|
33
48
|
const popoverContentStyle = (styles.popoverContent as any)({});
|
|
34
|
-
const iconColorStyle = (styles.iconColor as any)({ disabled });
|
|
35
49
|
|
|
36
50
|
// Format time to string
|
|
37
51
|
const formatTime = (date: Date | undefined): string => {
|
|
@@ -109,13 +123,18 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
109
123
|
onChange(date);
|
|
110
124
|
};
|
|
111
125
|
|
|
112
|
-
// Get web props
|
|
126
|
+
// Get web props for all elements
|
|
113
127
|
const containerProps = getWebProps([inputContainerStyle]);
|
|
128
|
+
const labelProps = getWebProps([labelTextStyle]);
|
|
129
|
+
const inputProps = getWebProps([textInputStyle]);
|
|
130
|
+
const iconButtonProps = getWebProps([iconButtonStyle]);
|
|
131
|
+
const errorProps = getWebProps([errorTextStyle]);
|
|
132
|
+
const popoverProps = getWebProps([popoverContentStyle]);
|
|
114
133
|
|
|
115
134
|
return (
|
|
116
135
|
<div style={style as React.CSSProperties}>
|
|
117
136
|
{label && (
|
|
118
|
-
<span
|
|
137
|
+
<span {...labelProps}>{label}</span>
|
|
119
138
|
)}
|
|
120
139
|
<div {...containerProps} ref={triggerRef}>
|
|
121
140
|
<input
|
|
@@ -125,18 +144,19 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
125
144
|
onBlur={handleInputBlur}
|
|
126
145
|
placeholder={placeholder}
|
|
127
146
|
disabled={disabled}
|
|
128
|
-
|
|
147
|
+
{...inputProps}
|
|
129
148
|
/>
|
|
130
149
|
<button
|
|
131
|
-
|
|
150
|
+
type="button"
|
|
151
|
+
{...iconButtonProps}
|
|
132
152
|
onClick={() => !disabled && setOpen(!open)}
|
|
133
153
|
disabled={disabled}
|
|
134
154
|
>
|
|
135
|
-
<IconSvg path={mdiClockOutline} size={
|
|
155
|
+
<IconSvg path={mdiClockOutline} size={iconSize} color={iconColor} />
|
|
136
156
|
</button>
|
|
137
157
|
</div>
|
|
138
158
|
{error && (
|
|
139
|
-
<span
|
|
159
|
+
<span {...errorProps}>{error}</span>
|
|
140
160
|
)}
|
|
141
161
|
|
|
142
162
|
<PositionedPortal
|
|
@@ -148,7 +168,7 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
148
168
|
onEscapeKey={() => setOpen(false)}
|
|
149
169
|
zIndex={9999}
|
|
150
170
|
>
|
|
151
|
-
<div
|
|
171
|
+
<div {...popoverProps}>
|
|
152
172
|
<TimePicker
|
|
153
173
|
value={value ?? undefined}
|
|
154
174
|
onChange={handleTimeChange}
|
package/src/TimePicker.styles.ts
CHANGED
|
@@ -20,11 +20,16 @@ export type TimePickerDynamicProps = {
|
|
|
20
20
|
*/
|
|
21
21
|
export const timePickerStyles = defineStyle('TimePicker', (theme: Theme) => ({
|
|
22
22
|
// Time picker container
|
|
23
|
-
timePicker: (
|
|
23
|
+
timePicker: (_props: TimePickerDynamicProps) => ({
|
|
24
24
|
padding: 12,
|
|
25
25
|
backgroundColor: theme.colors.surface.primary,
|
|
26
26
|
borderRadius: 6,
|
|
27
|
-
|
|
27
|
+
variants: {
|
|
28
|
+
disabled: {
|
|
29
|
+
true: { opacity: 0.6 },
|
|
30
|
+
false: { opacity: 1 },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
28
33
|
}),
|
|
29
34
|
|
|
30
35
|
// Time columns container
|
|
@@ -62,32 +67,51 @@ export const timePickerStyles = defineStyle('TimePicker', (theme: Theme) => ({
|
|
|
62
67
|
fontSize: 24,
|
|
63
68
|
fontWeight: '600' as const,
|
|
64
69
|
color: theme.colors.text.primary,
|
|
70
|
+
_web: {
|
|
71
|
+
pointerEvents: 'none',
|
|
72
|
+
},
|
|
65
73
|
}),
|
|
66
74
|
|
|
67
75
|
// Arrow button (up/down)
|
|
68
|
-
arrowButton: (
|
|
76
|
+
arrowButton: (_props: TimePickerDynamicProps) => ({
|
|
69
77
|
width: 32,
|
|
70
78
|
height: 32,
|
|
71
79
|
alignItems: 'center' as const,
|
|
72
80
|
justifyContent: 'center' as const,
|
|
73
81
|
borderRadius: 4,
|
|
74
|
-
|
|
82
|
+
backgroundColor: 'transparent',
|
|
83
|
+
borderWidth: 0,
|
|
75
84
|
_web: {
|
|
76
85
|
display: 'flex',
|
|
77
|
-
|
|
86
|
+
background: 'none',
|
|
87
|
+
border: 'none',
|
|
88
|
+
padding: 0,
|
|
89
|
+
},
|
|
90
|
+
variants: {
|
|
91
|
+
disabled: {
|
|
92
|
+
true: { opacity: 0.4, _web: { cursor: 'not-allowed' } },
|
|
93
|
+
false: { opacity: 1, _web: { cursor: 'pointer' } },
|
|
94
|
+
},
|
|
78
95
|
},
|
|
79
96
|
}),
|
|
80
97
|
|
|
81
98
|
// Period toggle button (AM/PM)
|
|
82
|
-
periodButton: (
|
|
99
|
+
periodButton: (_props: TimePickerDynamicProps) => ({
|
|
83
100
|
paddingVertical: 6,
|
|
84
101
|
paddingHorizontal: 12,
|
|
85
102
|
borderWidth: 1,
|
|
103
|
+
borderStyle: 'solid' as const,
|
|
86
104
|
borderColor: theme.colors.border.primary,
|
|
87
105
|
borderRadius: 4,
|
|
88
|
-
|
|
106
|
+
backgroundColor: 'transparent',
|
|
89
107
|
_web: {
|
|
90
|
-
|
|
108
|
+
background: 'none',
|
|
109
|
+
},
|
|
110
|
+
variants: {
|
|
111
|
+
disabled: {
|
|
112
|
+
true: { opacity: 0.4, _web: { cursor: 'not-allowed' } },
|
|
113
|
+
false: { opacity: 1, _web: { cursor: 'pointer' } },
|
|
114
|
+
},
|
|
91
115
|
},
|
|
92
116
|
}),
|
|
93
117
|
|
|
@@ -95,6 +119,9 @@ export const timePickerStyles = defineStyle('TimePicker', (theme: Theme) => ({
|
|
|
95
119
|
fontSize: 14,
|
|
96
120
|
fontWeight: '500' as const,
|
|
97
121
|
color: theme.colors.text.primary,
|
|
122
|
+
_web: {
|
|
123
|
+
pointerEvents: 'none',
|
|
124
|
+
},
|
|
98
125
|
}),
|
|
99
126
|
|
|
100
127
|
// Icon color helper
|
package/src/TimePicker.web.tsx
CHANGED
|
@@ -15,6 +15,11 @@ export const TimePicker: React.FC<TimePickerProps> = ({
|
|
|
15
15
|
}) => {
|
|
16
16
|
const styles = timePickerStyles;
|
|
17
17
|
|
|
18
|
+
// Apply variants for disabled state
|
|
19
|
+
styles.useVariants({
|
|
20
|
+
disabled,
|
|
21
|
+
});
|
|
22
|
+
|
|
18
23
|
const currentDate = value || new Date();
|
|
19
24
|
const hours = currentDate.getHours();
|
|
20
25
|
const minutes = currentDate.getMinutes();
|
|
@@ -67,10 +72,16 @@ export const TimePicker: React.FC<TimePickerProps> = ({
|
|
|
67
72
|
const periodButtonTextStyle = (styles.periodButtonText as any)({});
|
|
68
73
|
const iconColorStyle = (styles.iconColor as any)({});
|
|
69
74
|
|
|
70
|
-
// Get web props
|
|
75
|
+
// Get web props for all elements
|
|
71
76
|
const timePickerProps = getWebProps([timePickerStyle, style as any]);
|
|
72
77
|
const timeColumnsProps = getWebProps([timeColumnsStyle]);
|
|
73
78
|
const timeColumnProps = getWebProps([timeColumnStyle]);
|
|
79
|
+
const timeSeparatorProps = getWebProps([timeSeparatorStyle]);
|
|
80
|
+
const separatorTextProps = getWebProps([separatorTextStyle]);
|
|
81
|
+
const timeValueProps = getWebProps([timeValueStyle]);
|
|
82
|
+
const arrowButtonProps = getWebProps([arrowButtonStyle]);
|
|
83
|
+
const periodButtonProps = getWebProps([periodButtonStyle]);
|
|
84
|
+
const periodButtonTextProps = getWebProps([periodButtonTextStyle]);
|
|
74
85
|
|
|
75
86
|
return (
|
|
76
87
|
<div {...timePickerProps}>
|
|
@@ -78,17 +89,19 @@ export const TimePicker: React.FC<TimePickerProps> = ({
|
|
|
78
89
|
{/* Hours column */}
|
|
79
90
|
<div {...timeColumnProps}>
|
|
80
91
|
<button
|
|
81
|
-
|
|
92
|
+
type="button"
|
|
93
|
+
{...arrowButtonProps}
|
|
82
94
|
onClick={incrementHours}
|
|
83
95
|
disabled={disabled}
|
|
84
96
|
>
|
|
85
97
|
<IconSvg path={mdiChevronUp} size={20} color={iconColorStyle.color} />
|
|
86
98
|
</button>
|
|
87
|
-
<span
|
|
99
|
+
<span {...timeValueProps}>
|
|
88
100
|
{String(displayHours).padStart(2, '0')}
|
|
89
101
|
</span>
|
|
90
102
|
<button
|
|
91
|
-
|
|
103
|
+
type="button"
|
|
104
|
+
{...arrowButtonProps}
|
|
92
105
|
onClick={decrementHours}
|
|
93
106
|
disabled={disabled}
|
|
94
107
|
>
|
|
@@ -97,24 +110,26 @@ export const TimePicker: React.FC<TimePickerProps> = ({
|
|
|
97
110
|
</div>
|
|
98
111
|
|
|
99
112
|
{/* Separator */}
|
|
100
|
-
<div
|
|
101
|
-
<span
|
|
113
|
+
<div {...timeSeparatorProps}>
|
|
114
|
+
<span {...separatorTextProps}>:</span>
|
|
102
115
|
</div>
|
|
103
116
|
|
|
104
117
|
{/* Minutes column */}
|
|
105
118
|
<div {...timeColumnProps}>
|
|
106
119
|
<button
|
|
107
|
-
|
|
120
|
+
type="button"
|
|
121
|
+
{...arrowButtonProps}
|
|
108
122
|
onClick={incrementMinutes}
|
|
109
123
|
disabled={disabled}
|
|
110
124
|
>
|
|
111
125
|
<IconSvg path={mdiChevronUp} size={20} color={iconColorStyle.color} />
|
|
112
126
|
</button>
|
|
113
|
-
<span
|
|
127
|
+
<span {...timeValueProps}>
|
|
114
128
|
{String(minutes).padStart(2, '0')}
|
|
115
129
|
</span>
|
|
116
130
|
<button
|
|
117
|
-
|
|
131
|
+
type="button"
|
|
132
|
+
{...arrowButtonProps}
|
|
118
133
|
onClick={decrementMinutes}
|
|
119
134
|
disabled={disabled}
|
|
120
135
|
>
|
|
@@ -126,11 +141,12 @@ export const TimePicker: React.FC<TimePickerProps> = ({
|
|
|
126
141
|
{is12Hour && (
|
|
127
142
|
<div {...timeColumnProps}>
|
|
128
143
|
<button
|
|
129
|
-
|
|
144
|
+
type="button"
|
|
145
|
+
{...periodButtonProps}
|
|
130
146
|
onClick={togglePeriod}
|
|
131
147
|
disabled={disabled}
|
|
132
148
|
>
|
|
133
|
-
<span
|
|
149
|
+
<span {...periodButtonTextProps}>
|
|
134
150
|
{isPM ? 'PM' : 'AM'}
|
|
135
151
|
</span>
|
|
136
152
|
</button>
|
|
@@ -79,8 +79,8 @@ export const DatePickerExamples = () => {
|
|
|
79
79
|
<View gap="md">
|
|
80
80
|
<Text typography="h4" weight="semibold">With Min/Max Date</Text>
|
|
81
81
|
<DateInput
|
|
82
|
-
value={undefined}
|
|
83
|
-
onChange={
|
|
82
|
+
value={date ?? undefined}
|
|
83
|
+
onChange={setDate}
|
|
84
84
|
label="Future Dates Only"
|
|
85
85
|
placeholder="MM/DD/YYYY"
|
|
86
86
|
minDate={tomorrow}
|
|
@@ -91,6 +91,129 @@ export const DatePickerExamples = () => {
|
|
|
91
91
|
</Text>
|
|
92
92
|
</View>
|
|
93
93
|
|
|
94
|
+
{/* Size Variants */}
|
|
95
|
+
<View gap="md">
|
|
96
|
+
<Text typography="h4" weight="semibold">Size Variants</Text>
|
|
97
|
+
<Text typography="caption" color="secondary">
|
|
98
|
+
DateInput, TimeInput, and DateTimePicker support different sizes
|
|
99
|
+
</Text>
|
|
100
|
+
|
|
101
|
+
<View gap="sm">
|
|
102
|
+
<Text typography="body2" weight="medium">Extra Small (xs)</Text>
|
|
103
|
+
<View gap="xs">
|
|
104
|
+
<DateInput
|
|
105
|
+
value={date ?? undefined}
|
|
106
|
+
onChange={setDate}
|
|
107
|
+
placeholder="MM/DD/YYYY"
|
|
108
|
+
size="xs"
|
|
109
|
+
/>
|
|
110
|
+
<TimeInput
|
|
111
|
+
value={time ?? undefined}
|
|
112
|
+
onChange={setTime}
|
|
113
|
+
placeholder="12:00 PM"
|
|
114
|
+
size="xs"
|
|
115
|
+
/>
|
|
116
|
+
</View>
|
|
117
|
+
</View>
|
|
118
|
+
|
|
119
|
+
<View gap="sm">
|
|
120
|
+
<Text typography="body2" weight="medium">Small (sm)</Text>
|
|
121
|
+
<View gap="xs">
|
|
122
|
+
<DateInput
|
|
123
|
+
value={date ?? undefined}
|
|
124
|
+
onChange={setDate}
|
|
125
|
+
placeholder="MM/DD/YYYY"
|
|
126
|
+
size="sm"
|
|
127
|
+
/>
|
|
128
|
+
<TimeInput
|
|
129
|
+
value={time ?? undefined}
|
|
130
|
+
onChange={setTime}
|
|
131
|
+
placeholder="12:00 PM"
|
|
132
|
+
size="sm"
|
|
133
|
+
/>
|
|
134
|
+
</View>
|
|
135
|
+
</View>
|
|
136
|
+
|
|
137
|
+
<View gap="sm">
|
|
138
|
+
<Text typography="body2" weight="medium">Medium (md) - Default</Text>
|
|
139
|
+
<View gap="xs">
|
|
140
|
+
<DateInput
|
|
141
|
+
value={date ?? undefined}
|
|
142
|
+
onChange={setDate}
|
|
143
|
+
placeholder="MM/DD/YYYY"
|
|
144
|
+
size="md"
|
|
145
|
+
/>
|
|
146
|
+
<TimeInput
|
|
147
|
+
value={time ?? undefined}
|
|
148
|
+
onChange={setTime}
|
|
149
|
+
placeholder="12:00 PM"
|
|
150
|
+
size="md"
|
|
151
|
+
/>
|
|
152
|
+
</View>
|
|
153
|
+
</View>
|
|
154
|
+
|
|
155
|
+
<View gap="sm">
|
|
156
|
+
<Text typography="body2" weight="medium">Large (lg)</Text>
|
|
157
|
+
<View gap="xs">
|
|
158
|
+
<DateInput
|
|
159
|
+
value={date ?? undefined}
|
|
160
|
+
onChange={setDate}
|
|
161
|
+
placeholder="MM/DD/YYYY"
|
|
162
|
+
size="lg"
|
|
163
|
+
/>
|
|
164
|
+
<TimeInput
|
|
165
|
+
value={time ?? undefined}
|
|
166
|
+
onChange={setTime}
|
|
167
|
+
placeholder="12:00 PM"
|
|
168
|
+
size="lg"
|
|
169
|
+
/>
|
|
170
|
+
</View>
|
|
171
|
+
</View>
|
|
172
|
+
|
|
173
|
+
<View gap="sm">
|
|
174
|
+
<Text typography="body2" weight="medium">Extra Large (xl)</Text>
|
|
175
|
+
<View gap="xs">
|
|
176
|
+
<DateInput
|
|
177
|
+
value={date ?? undefined}
|
|
178
|
+
onChange={setDate}
|
|
179
|
+
placeholder="MM/DD/YYYY"
|
|
180
|
+
size="xl"
|
|
181
|
+
/>
|
|
182
|
+
<TimeInput
|
|
183
|
+
value={time ?? undefined}
|
|
184
|
+
onChange={setTime}
|
|
185
|
+
placeholder="12:00 PM"
|
|
186
|
+
size="xl"
|
|
187
|
+
/>
|
|
188
|
+
</View>
|
|
189
|
+
</View>
|
|
190
|
+
</View>
|
|
191
|
+
|
|
192
|
+
{/* DateTimePicker Sizes */}
|
|
193
|
+
<View gap="md">
|
|
194
|
+
<Text typography="h4" weight="semibold">DateTimePicker Sizes</Text>
|
|
195
|
+
<View gap="sm">
|
|
196
|
+
<DateTimePicker
|
|
197
|
+
value={dateTime ?? undefined}
|
|
198
|
+
onChange={setDateTime}
|
|
199
|
+
label="Small"
|
|
200
|
+
size="sm"
|
|
201
|
+
/>
|
|
202
|
+
<DateTimePicker
|
|
203
|
+
value={dateTime ?? undefined}
|
|
204
|
+
onChange={setDateTime}
|
|
205
|
+
label="Medium (Default)"
|
|
206
|
+
size="md"
|
|
207
|
+
/>
|
|
208
|
+
<DateTimePicker
|
|
209
|
+
value={dateTime ?? undefined}
|
|
210
|
+
onChange={setDateTime}
|
|
211
|
+
label="Large"
|
|
212
|
+
size="lg"
|
|
213
|
+
/>
|
|
214
|
+
</View>
|
|
215
|
+
</View>
|
|
216
|
+
|
|
94
217
|
{/* Inline DatePicker */}
|
|
95
218
|
<View gap="md">
|
|
96
219
|
<Text typography="h4" weight="semibold">Inline DatePicker</Text>
|
|
@@ -120,8 +243,8 @@ export const DatePickerExamples = () => {
|
|
|
120
243
|
<View gap="md">
|
|
121
244
|
<Text typography="h4" weight="semibold">24-hour Format</Text>
|
|
122
245
|
<TimeInput
|
|
123
|
-
value={undefined}
|
|
124
|
-
onChange={
|
|
246
|
+
value={time ?? undefined}
|
|
247
|
+
onChange={setTime}
|
|
125
248
|
label="24-hour Time"
|
|
126
249
|
placeholder="14:30"
|
|
127
250
|
mode="24h"
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ViewStyle } from 'react-native';
|
|
2
|
+
import type { Size } from '@idealyst/theme';
|
|
2
3
|
|
|
3
4
|
export interface DatePickerProps {
|
|
4
5
|
value?: Date;
|
|
@@ -27,6 +28,7 @@ export interface DateInputProps {
|
|
|
27
28
|
maxDate?: Date;
|
|
28
29
|
disabled?: boolean;
|
|
29
30
|
error?: string;
|
|
31
|
+
size?: Size;
|
|
30
32
|
style?: ViewStyle;
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -39,6 +41,7 @@ export interface TimeInputProps {
|
|
|
39
41
|
minuteStep?: number;
|
|
40
42
|
disabled?: boolean;
|
|
41
43
|
error?: string;
|
|
44
|
+
size?: Size;
|
|
42
45
|
style?: ViewStyle;
|
|
43
46
|
}
|
|
44
47
|
|
|
@@ -52,5 +55,6 @@ export interface DateTimePickerProps {
|
|
|
52
55
|
minuteStep?: number;
|
|
53
56
|
disabled?: boolean;
|
|
54
57
|
error?: string;
|
|
58
|
+
size?: Size;
|
|
55
59
|
style?: ViewStyle;
|
|
56
60
|
}
|