@idealyst/datepicker 1.2.118 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idealyst/datepicker",
3
- "version": "1.2.118",
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": "^1.2.118",
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": "^1.2.118",
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",
@@ -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
- <View style={inputContainerStyle}>
102
- <TextInput
103
- value={inputValue}
104
- onChangeText={handleInputChange}
105
- onBlur={handleInputBlur}
106
- placeholder={placeholder}
107
- editable={!disabled}
108
- style={textInputStyle}
109
- />
102
+ {pressable ? (
110
103
  <TouchableOpacity
111
- style={iconButtonStyle}
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
- </View>
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}
@@ -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
- <div {...containerProps} ref={triggerRef}>
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
- {...iconButtonProps}
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
- </div>
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 complete last week)
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 remaining = 7 - (days.length % 7);
77
- if (remaining < 7) {
78
- for (let day = 1; day <= remaining; day++) {
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: 28 }} />
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={[dayButtonStyle, dayDisabled && disabledDayButtonStyle]}
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
  })}
@@ -19,12 +19,12 @@ export type DatePickerDynamicProps = {
19
19
  * DatePicker calendar styles with theme reactivity.
20
20
  */
21
21
  export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme: Theme) => ({
22
- // Calendar container - compact
22
+ // Calendar container
23
23
  calendar: (_props: DatePickerDynamicProps) => ({
24
- padding: 8,
24
+ padding: 12,
25
25
  backgroundColor: theme.colors.surface.primary,
26
26
  borderRadius: 6,
27
- width: 220,
27
+ width: 280,
28
28
  variants: {
29
29
  disabled: {
30
30
  true: { opacity: 0.6 },
@@ -38,7 +38,7 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
38
38
  flexDirection: 'row' as const,
39
39
  alignItems: 'center' as const,
40
40
  justifyContent: 'space-between' as const,
41
- marginBottom: 4,
41
+ marginBottom: 8,
42
42
  paddingHorizontal: 2,
43
43
  _web: {
44
44
  display: 'flex',
@@ -57,15 +57,15 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
57
57
  // Weekday header row
58
58
  weekdayRow: (_props: DatePickerDynamicProps) => ({
59
59
  flexDirection: 'row' as const,
60
- marginBottom: 2,
60
+ marginBottom: 4,
61
61
  _web: {
62
62
  display: 'flex',
63
63
  },
64
64
  }),
65
65
 
66
66
  weekdayCell: (_props: DatePickerDynamicProps) => ({
67
- width: 28,
68
- height: 20,
67
+ width: 36,
68
+ height: 24,
69
69
  alignItems: 'center' as const,
70
70
  justifyContent: 'center' as const,
71
71
  _web: {
@@ -104,12 +104,13 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
104
104
  },
105
105
  }),
106
106
 
107
- // Individual day cell - compact
107
+ // Individual day cell - contains button + indicator row
108
108
  dayCell: (_props: DatePickerDynamicProps) => ({
109
- width: 28,
110
- height: 28,
109
+ width: 36,
110
+ height: 42,
111
111
  alignItems: 'center' as const,
112
- justifyContent: 'center' as const,
112
+ justifyContent: 'flex-start' as const,
113
+ paddingTop: 2,
113
114
  _web: {
114
115
  display: 'flex',
115
116
  },
@@ -117,8 +118,8 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
117
118
 
118
119
  // Navigation button
119
120
  navButton: (_props: DatePickerDynamicProps) => ({
120
- width: 28,
121
- height: 28,
121
+ width: 32,
122
+ height: 32,
122
123
  alignItems: 'center' as const,
123
124
  justifyContent: 'center' as const,
124
125
  borderRadius: 4,
@@ -158,18 +159,18 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
158
159
  }),
159
160
 
160
161
  titleText: (_props: DatePickerDynamicProps) => ({
161
- fontSize: 14,
162
+ fontSize: 15,
162
163
  fontWeight: '600' as const,
163
164
  color: theme.colors.text.primary,
164
165
  }),
165
166
 
166
- // Day button - fills entire cell for better click handling
167
+ // Day button - fills cell for better click handling
167
168
  dayButton: (_props: DatePickerDynamicProps) => ({
168
- width: 28,
169
- height: 28,
169
+ width: 32,
170
+ height: 32,
170
171
  alignItems: 'center' as const,
171
172
  justifyContent: 'center' as const,
172
- borderRadius: 14,
173
+ borderRadius: 16,
173
174
  backgroundColor: 'transparent',
174
175
  borderWidth: 0,
175
176
  _web: {
@@ -188,7 +189,7 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
188
189
  }),
189
190
 
190
191
  dayText: (_props: DatePickerDynamicProps) => ({
191
- fontSize: 12,
192
+ fontSize: 14,
192
193
  color: theme.colors.text.primary,
193
194
  _web: {
194
195
  pointerEvents: 'none',
@@ -196,7 +197,7 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
196
197
  }),
197
198
 
198
199
  weekdayText: (_props: DatePickerDynamicProps) => ({
199
- fontSize: 11,
200
+ fontSize: 12,
200
201
  color: theme.colors.text.secondary,
201
202
  }),
202
203
 
@@ -232,7 +233,7 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
232
233
  }),
233
234
 
234
235
  selectorItemText: (_props: DatePickerDynamicProps) => ({
235
- fontSize: 12,
236
+ fontSize: 13,
236
237
  color: theme.colors.text.primary,
237
238
  _web: {
238
239
  pointerEvents: 'none',
@@ -249,7 +250,7 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
249
250
  // Selected day styling
250
251
  selectedDay: (_props: DatePickerDynamicProps) => ({
251
252
  backgroundColor: theme.intents.primary.primary,
252
- borderRadius: 6,
253
+ borderRadius: 16,
253
254
  _web: {
254
255
  background: theme.intents.primary.primary,
255
256
  },
@@ -262,15 +263,37 @@ export const datePickerCalendarStyles = defineStyle('DatePickerCalendar', (theme
262
263
  },
263
264
  }),
264
265
 
265
- // Today styling - subtle background highlight
266
+ // Today styling - outlined circle (no fill)
266
267
  todayDay: (_props: DatePickerDynamicProps) => ({
267
- backgroundColor: theme.intents.primary.light,
268
- borderRadius: 6,
268
+ borderWidth: 1,
269
+ borderColor: theme.intents.primary.primary,
270
+ borderRadius: 16,
271
+ backgroundColor: 'transparent',
269
272
  _web: {
270
- background: theme.intents.primary.light,
273
+ background: 'transparent',
274
+ boxSizing: 'border-box',
271
275
  },
272
276
  }),
273
277
 
278
+ // Indicator row below the day number
279
+ indicatorRow: (_props: DatePickerDynamicProps) => ({
280
+ flexDirection: 'row' as const,
281
+ alignItems: 'center' as const,
282
+ justifyContent: 'center' as const,
283
+ height: 6,
284
+ gap: 2,
285
+ _web: {
286
+ display: 'flex',
287
+ },
288
+ }),
289
+
290
+ // Individual indicator dot
291
+ indicator: (_props: DatePickerDynamicProps) => ({
292
+ width: 4,
293
+ height: 4,
294
+ borderRadius: 2,
295
+ }),
296
+
274
297
  // Icon color helper
275
298
  iconColor: (_props: DatePickerDynamicProps) => ({
276
299
  color: theme.colors.text.primary,
@@ -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 complete last week)
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 remaining = 7 - (days.length % 7);
60
- if (remaining < 7) {
61
- for (let day = 1; day <= remaining; day++) {
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: 28 }} />
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
- <button
347
- key={index}
348
- type="button"
349
- className={buttonProps.className}
350
- style={{ opacity: (!isCurrentMonthDay || dayDisabled) ? 0.3 : 1 }}
351
- onClick={() => handleDayPress(date)}
352
- disabled={dayDisabled}
353
- >
354
- <span {...(selected ? selectedDayTextProps : dayTextProps)}>
355
- {date.getDate()}
356
- </span>
357
- </button>
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>
@@ -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,
@@ -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
- <View style={inputContainerStyle}>
122
- <TextInput
123
- value={inputValue}
124
- onChangeText={handleInputChange}
125
- onBlur={handleInputBlur}
126
- placeholder={placeholder}
127
- editable={!disabled}
128
- style={textInputStyle}
129
- />
122
+ {pressable ? (
130
123
  <TouchableOpacity
131
- style={iconButtonStyle}
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
- </View>
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}
@@ -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
- <div {...containerProps} ref={triggerRef}>
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
- {...iconButtonProps}
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
- </div>
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
  )}
@@ -12,4 +12,5 @@ export type {
12
12
  DateInputProps,
13
13
  TimeInputProps,
14
14
  DateTimePickerProps,
15
+ DayIndicator,
15
16
  } from './types';
package/src/index.ts CHANGED
@@ -12,4 +12,5 @@ export type {
12
12
  DateInputProps,
13
13
  TimeInputProps,
14
14
  DateTimePickerProps,
15
+ DayIndicator,
15
16
  } from './types';
package/src/types.ts CHANGED
@@ -1,12 +1,19 @@
1
1
  import type { ViewStyle } from 'react-native';
2
2
  import type { Size } from '@idealyst/theme';
3
3
 
4
+ export interface DayIndicator {
5
+ color: string;
6
+ key?: string;
7
+ }
8
+
4
9
  export interface DatePickerProps {
5
10
  value?: Date;
6
11
  onChange: (date: Date) => void;
7
12
  minDate?: Date;
8
13
  maxDate?: Date;
9
14
  disabled?: boolean;
15
+ /** Indicators to render below specific dates. Key format: "YYYY-MM-DD" */
16
+ indicators?: Record<string, DayIndicator[]>;
10
17
  style?: ViewStyle;
11
18
  }
12
19
 
@@ -27,6 +34,8 @@ export interface DateInputProps {
27
34
  minDate?: Date;
28
35
  maxDate?: Date;
29
36
  disabled?: boolean;
37
+ /** When true, the entire input area is pressable to open the calendar instead of being a text input. */
38
+ pressable?: boolean;
30
39
  error?: string;
31
40
  size?: Size;
32
41
  style?: ViewStyle;
@@ -40,6 +49,8 @@ export interface TimeInputProps {
40
49
  mode?: '12h' | '24h';
41
50
  minuteStep?: number;
42
51
  disabled?: boolean;
52
+ /** When true, the entire input area is pressable to open the time picker instead of being a text input. */
53
+ pressable?: boolean;
43
54
  error?: string;
44
55
  size?: Size;
45
56
  style?: ViewStyle;
@@ -54,6 +65,8 @@ export interface DateTimePickerProps {
54
65
  timeMode?: '12h' | '24h';
55
66
  minuteStep?: number;
56
67
  disabled?: boolean;
68
+ /** When true, both input areas are pressable to open their pickers instead of being text inputs. */
69
+ pressable?: boolean;
57
70
  error?: string;
58
71
  size?: Size;
59
72
  style?: ViewStyle;