@idealyst/datepicker 1.1.4 → 1.1.5

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.
Files changed (80) hide show
  1. package/package.json +6 -5
  2. package/src/DateInput.native.tsx +155 -0
  3. package/src/DateInput.tsx +2 -0
  4. package/src/DateInput.web.tsx +146 -0
  5. package/src/DatePicker.tsx +276 -0
  6. package/src/DateTimePicker.tsx +89 -0
  7. package/src/TimeInput.native.tsx +175 -0
  8. package/src/TimeInput.tsx +2 -0
  9. package/src/TimeInput.web.tsx +171 -0
  10. package/src/TimePicker.tsx +106 -0
  11. package/src/examples/DatePickerExamples.tsx +113 -149
  12. package/src/examples/index.ts +1 -1
  13. package/src/index.native.ts +15 -20
  14. package/src/index.ts +14 -19
  15. package/src/styles.ts +127 -0
  16. package/src/types.ts +56 -0
  17. package/src/DateInput/DateInput.native.tsx +0 -61
  18. package/src/DateInput/DateInput.styles.tsx +0 -26
  19. package/src/DateInput/DateInput.web.tsx +0 -61
  20. package/src/DateInput/DateInputBase.tsx +0 -228
  21. package/src/DateInput/index.native.ts +0 -2
  22. package/src/DateInput/index.ts +0 -2
  23. package/src/DateInput/types.ts +0 -60
  24. package/src/DatePicker/Calendar.native.tsx +0 -261
  25. package/src/DatePicker/Calendar.styles.tsx +0 -230
  26. package/src/DatePicker/Calendar.web.tsx +0 -159
  27. package/src/DatePicker/DatePicker.native.tsx +0 -51
  28. package/src/DatePicker/DatePicker.styles.tsx +0 -76
  29. package/src/DatePicker/DatePicker.web.tsx +0 -31
  30. package/src/DatePicker/index.native.ts +0 -3
  31. package/src/DatePicker/index.ts +0 -3
  32. package/src/DatePicker/types.ts +0 -78
  33. package/src/DateRangePicker/DateRangePicker.native.tsx +0 -39
  34. package/src/DateRangePicker/DateRangePicker.styles.tsx +0 -83
  35. package/src/DateRangePicker/DateRangePicker.web.tsx +0 -40
  36. package/src/DateRangePicker/RangeCalendar.native.tsx +0 -355
  37. package/src/DateRangePicker/RangeCalendar.styles.tsx +0 -200
  38. package/src/DateRangePicker/RangeCalendar.web.tsx +0 -384
  39. package/src/DateRangePicker/index.native.ts +0 -3
  40. package/src/DateRangePicker/index.ts +0 -3
  41. package/src/DateRangePicker/types.ts +0 -107
  42. package/src/DateTimePicker/DateTimePicker.native.tsx +0 -24
  43. package/src/DateTimePicker/DateTimePicker.styles.tsx +0 -104
  44. package/src/DateTimePicker/DateTimePicker.tsx +0 -21
  45. package/src/DateTimePicker/DateTimePickerBase.tsx +0 -185
  46. package/src/DateTimePicker/TimePicker.native.tsx +0 -17
  47. package/src/DateTimePicker/TimePicker.styles.tsx +0 -115
  48. package/src/DateTimePicker/TimePicker.tsx +0 -14
  49. package/src/DateTimePicker/TimePickerBase.tsx +0 -232
  50. package/src/DateTimePicker/index.native.ts +0 -3
  51. package/src/DateTimePicker/index.ts +0 -3
  52. package/src/DateTimePicker/primitives/ClockFace.native.tsx +0 -195
  53. package/src/DateTimePicker/primitives/ClockFace.web.tsx +0 -168
  54. package/src/DateTimePicker/primitives/TimeInput.native.tsx +0 -53
  55. package/src/DateTimePicker/primitives/TimeInput.web.tsx +0 -66
  56. package/src/DateTimePicker/primitives/index.native.ts +0 -2
  57. package/src/DateTimePicker/primitives/index.ts +0 -2
  58. package/src/DateTimePicker/primitives/index.web.ts +0 -2
  59. package/src/DateTimePicker/types.ts +0 -80
  60. package/src/DateTimePicker/utils/dimensions.native.ts +0 -9
  61. package/src/DateTimePicker/utils/dimensions.ts +0 -9
  62. package/src/DateTimePicker/utils/dimensions.web.ts +0 -33
  63. package/src/DateTimeRangePicker/DateTimeRangePicker.native.tsx +0 -24
  64. package/src/DateTimeRangePicker/DateTimeRangePicker.styles.tsx +0 -118
  65. package/src/DateTimeRangePicker/DateTimeRangePicker.web.tsx +0 -21
  66. package/src/DateTimeRangePicker/DateTimeRangePickerBase.tsx +0 -327
  67. package/src/DateTimeRangePicker/index.native.ts +0 -2
  68. package/src/DateTimeRangePicker/index.ts +0 -2
  69. package/src/DateTimeRangePicker/types.ts +0 -70
  70. package/src/primitives/CalendarGrid/CalendarGrid.styles.tsx +0 -51
  71. package/src/primitives/CalendarGrid/CalendarGrid.tsx +0 -146
  72. package/src/primitives/CalendarGrid/index.ts +0 -1
  73. package/src/primitives/CalendarHeader/CalendarHeader.styles.tsx +0 -25
  74. package/src/primitives/CalendarHeader/CalendarHeader.tsx +0 -69
  75. package/src/primitives/CalendarHeader/index.ts +0 -1
  76. package/src/primitives/CalendarOverlay/CalendarOverlay.styles.tsx +0 -86
  77. package/src/primitives/CalendarOverlay/CalendarOverlay.tsx +0 -136
  78. package/src/primitives/CalendarOverlay/index.ts +0 -1
  79. package/src/primitives/Wrapper/Wrapper.web.tsx +0 -33
  80. package/src/primitives/Wrapper/index.ts +0 -1
@@ -0,0 +1,175 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Modal, TextInput as RNTextInput } from 'react-native';
3
+ import { View, Text, Button, Icon } from '@idealyst/components';
4
+ import { TimePicker } from './TimePicker';
5
+ import { datePickerStyles } from './styles';
6
+ import type { TimeInputProps } from './types';
7
+
8
+ export const TimeInput: React.FC<TimeInputProps> = ({
9
+ value,
10
+ onChange,
11
+ label,
12
+ placeholder = '12:00 PM',
13
+ mode = '12h',
14
+ minuteStep = 1,
15
+ disabled = false,
16
+ error,
17
+ style,
18
+ }) => {
19
+ const [open, setOpen] = useState(false);
20
+ const [inputValue, setInputValue] = useState('');
21
+
22
+ // Format time to string
23
+ const formatTime = (date: Date | undefined): string => {
24
+ if (!date) return '';
25
+ const hours = date.getHours();
26
+ const minutes = date.getMinutes();
27
+
28
+ if (mode === '24h') {
29
+ return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
30
+ }
31
+
32
+ const period = hours >= 12 ? 'PM' : 'AM';
33
+ const displayHours = hours % 12 || 12;
34
+ return `${displayHours}:${String(minutes).padStart(2, '0')} ${period}`;
35
+ };
36
+
37
+ // Parse string to date (time only)
38
+ const parseTime = (str: string): Date | null => {
39
+ const match12h = str.match(/^(\d{1,2}):(\d{2})\s*(AM|PM)$/i);
40
+ if (match12h) {
41
+ let [, hours, minutes, period] = match12h;
42
+ let h = parseInt(hours);
43
+ const m = parseInt(minutes);
44
+ if (h < 1 || h > 12 || m > 59) return null;
45
+ if (period.toUpperCase() === 'PM' && h !== 12) h += 12;
46
+ if (period.toUpperCase() === 'AM' && h === 12) h = 0;
47
+ const date = new Date();
48
+ date.setHours(h, m, 0, 0);
49
+ return date;
50
+ }
51
+
52
+ const match24h = str.match(/^(\d{1,2}):(\d{2})$/);
53
+ if (match24h) {
54
+ const [, hours, minutes] = match24h;
55
+ const h = parseInt(hours);
56
+ const m = parseInt(minutes);
57
+ if (h > 23 || m > 59) return null;
58
+ const date = new Date();
59
+ date.setHours(h, m, 0, 0);
60
+ return date;
61
+ }
62
+
63
+ return null;
64
+ };
65
+
66
+ useEffect(() => {
67
+ setInputValue(formatTime(value ?? undefined));
68
+ }, [value, mode]);
69
+
70
+ const handleInputChange = (newValue: string) => {
71
+ setInputValue(newValue);
72
+ const parsed = parseTime(newValue);
73
+ if (parsed) {
74
+ onChange(parsed);
75
+ }
76
+ };
77
+
78
+ const handleInputBlur = () => {
79
+ const parsed = parseTime(inputValue);
80
+ if (!parsed && value) {
81
+ setInputValue(formatTime(value));
82
+ } else if (!parsed) {
83
+ setInputValue('');
84
+ onChange(null);
85
+ }
86
+ };
87
+
88
+ const handleTimeChange = (date: Date) => {
89
+ onChange(date);
90
+ };
91
+
92
+ return (
93
+ <View style={style}>
94
+ {label && (
95
+ <Text typography="body2" weight="medium" style={{ marginBottom: 4 }}>
96
+ {label}
97
+ </Text>
98
+ )}
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
+ >
110
+ <RNTextInput
111
+ value={inputValue}
112
+ onChangeText={handleInputChange}
113
+ onBlur={handleInputBlur}
114
+ placeholder={placeholder}
115
+ 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
+ }}
124
+ />
125
+ <Button
126
+ type="text"
127
+ size="sm"
128
+ onPress={() => !disabled && setOpen(true)}
129
+ disabled={disabled}
130
+ style={{ marginRight: 4 }}
131
+ >
132
+ <Icon name="clock-outline" size={18} />
133
+ </Button>
134
+ </View>
135
+ {error && (
136
+ <Text typography="caption" style={{ marginTop: 4, color: '#ef4444' }}>
137
+ {error}
138
+ </Text>
139
+ )}
140
+
141
+ <Modal
142
+ visible={open}
143
+ transparent
144
+ animationType="fade"
145
+ onRequestClose={() => setOpen(false)}
146
+ >
147
+ <View
148
+ style={{
149
+ flex: 1,
150
+ justifyContent: 'center',
151
+ alignItems: 'center',
152
+ backgroundColor: 'rgba(0,0,0,0.5)',
153
+ }}
154
+ >
155
+ <View style={datePickerStyles.popoverContent}>
156
+ <TimePicker
157
+ value={value ?? undefined}
158
+ onChange={handleTimeChange}
159
+ mode={mode}
160
+ minuteStep={minuteStep}
161
+ disabled={disabled}
162
+ />
163
+ <Button
164
+ type="text"
165
+ onPress={() => setOpen(false)}
166
+ style={{ marginTop: 8 }}
167
+ >
168
+ Close
169
+ </Button>
170
+ </View>
171
+ </View>
172
+ </Modal>
173
+ </View>
174
+ );
175
+ };
@@ -0,0 +1,2 @@
1
+ // Platform-specific exports
2
+ export { TimeInput } from './TimeInput.web';
@@ -0,0 +1,171 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { View, Text, Button, Icon } from '@idealyst/components';
3
+ import { PositionedPortal } from '@idealyst/components/internal';
4
+ import { TimePicker } from './TimePicker';
5
+ import { datePickerStyles } from './styles';
6
+ import type { TimeInputProps } from './types';
7
+
8
+ export const TimeInput: React.FC<TimeInputProps> = ({
9
+ value,
10
+ onChange,
11
+ label,
12
+ placeholder = '12:00 PM',
13
+ mode = '12h',
14
+ minuteStep = 1,
15
+ disabled = false,
16
+ error,
17
+ style,
18
+ }) => {
19
+ const [open, setOpen] = useState(false);
20
+ const [inputValue, setInputValue] = useState('');
21
+ const triggerRef = useRef<HTMLDivElement>(null);
22
+
23
+ // Format time to string
24
+ const formatTime = (date: Date | undefined): string => {
25
+ if (!date) return '';
26
+ const hours = date.getHours();
27
+ const minutes = date.getMinutes();
28
+
29
+ if (mode === '24h') {
30
+ return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
31
+ }
32
+
33
+ const period = hours >= 12 ? 'PM' : 'AM';
34
+ const displayHours = hours % 12 || 12;
35
+ return `${displayHours}:${String(minutes).padStart(2, '0')} ${period}`;
36
+ };
37
+
38
+ // Parse string to date (time only)
39
+ const parseTime = (str: string): Date | null => {
40
+ // Try 12h format: "1:30 PM" or "12:00 AM"
41
+ const match12h = str.match(/^(\d{1,2}):(\d{2})\s*(AM|PM)$/i);
42
+ if (match12h) {
43
+ let [, hours, minutes, period] = match12h;
44
+ let h = parseInt(hours);
45
+ const m = parseInt(minutes);
46
+ if (h < 1 || h > 12 || m > 59) return null;
47
+ if (period.toUpperCase() === 'PM' && h !== 12) h += 12;
48
+ if (period.toUpperCase() === 'AM' && h === 12) h = 0;
49
+ const date = new Date();
50
+ date.setHours(h, m, 0, 0);
51
+ return date;
52
+ }
53
+
54
+ // Try 24h format: "13:30"
55
+ const match24h = str.match(/^(\d{1,2}):(\d{2})$/);
56
+ if (match24h) {
57
+ const [, hours, minutes] = match24h;
58
+ const h = parseInt(hours);
59
+ const m = parseInt(minutes);
60
+ if (h > 23 || m > 59) return null;
61
+ const date = new Date();
62
+ date.setHours(h, m, 0, 0);
63
+ return date;
64
+ }
65
+
66
+ return null;
67
+ };
68
+
69
+ // Sync input value with prop value
70
+ useEffect(() => {
71
+ setInputValue(formatTime(value ?? undefined));
72
+ }, [value, mode]);
73
+
74
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
75
+ const newValue = e.target.value;
76
+ setInputValue(newValue);
77
+
78
+ // Try to parse and update if valid
79
+ const parsed = parseTime(newValue);
80
+ if (parsed) {
81
+ onChange(parsed);
82
+ }
83
+ };
84
+
85
+ const handleInputBlur = () => {
86
+ const parsed = parseTime(inputValue);
87
+ if (!parsed && value) {
88
+ setInputValue(formatTime(value));
89
+ } else if (!parsed) {
90
+ setInputValue('');
91
+ onChange(null);
92
+ }
93
+ };
94
+
95
+ const handleTimeChange = (date: Date) => {
96
+ onChange(date);
97
+ };
98
+
99
+ return (
100
+ <View style={style}>
101
+ {label && (
102
+ <Text typography="body2" weight="medium" style={{ marginBottom: 4 }}>
103
+ {label}
104
+ </Text>
105
+ )}
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
+ <input
118
+ type="text"
119
+ value={inputValue}
120
+ onChange={handleInputChange}
121
+ onBlur={handleInputBlur}
122
+ placeholder={placeholder}
123
+ disabled={disabled}
124
+ style={{
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
+ }}
133
+ />
134
+ <Button
135
+ type="text"
136
+ size="sm"
137
+ onPress={() => !disabled && setOpen(!open)}
138
+ disabled={disabled}
139
+ style={{ marginRight: 4 }}
140
+ >
141
+ <Icon name="clock-outline" size={18} />
142
+ </Button>
143
+ </div>
144
+ {error && (
145
+ <Text typography="caption" style={{ marginTop: 4, color: '#ef4444' }}>
146
+ {error}
147
+ </Text>
148
+ )}
149
+
150
+ <PositionedPortal
151
+ open={open}
152
+ anchor={triggerRef}
153
+ placement="bottom-start"
154
+ offset={4}
155
+ onClickOutside={() => setOpen(false)}
156
+ onEscapeKey={() => setOpen(false)}
157
+ zIndex={9999}
158
+ >
159
+ <View style={datePickerStyles.popoverContent}>
160
+ <TimePicker
161
+ value={value ?? undefined}
162
+ onChange={handleTimeChange}
163
+ mode={mode}
164
+ minuteStep={minuteStep}
165
+ disabled={disabled}
166
+ />
167
+ </View>
168
+ </PositionedPortal>
169
+ </View>
170
+ );
171
+ };
@@ -0,0 +1,106 @@
1
+ import React from 'react';
2
+ import { View, Text, Button, Icon } from '@idealyst/components';
3
+ import { datePickerStyles } from './styles';
4
+ import type { TimePickerProps } from './types';
5
+
6
+ export const TimePicker: React.FC<TimePickerProps> = ({
7
+ value,
8
+ onChange,
9
+ mode = '12h',
10
+ minuteStep = 1,
11
+ disabled = false,
12
+ style,
13
+ }) => {
14
+ datePickerStyles.useVariants({ disabled });
15
+
16
+ const currentDate = value || new Date();
17
+ const hours = currentDate.getHours();
18
+ const minutes = currentDate.getMinutes();
19
+ const is12Hour = mode === '12h';
20
+ const isPM = hours >= 12;
21
+ const displayHours = is12Hour ? (hours % 12 || 12) : hours;
22
+
23
+ const updateTime = (newHours: number, newMinutes: number) => {
24
+ const updated = new Date(currentDate);
25
+ updated.setHours(newHours, newMinutes, 0, 0);
26
+ onChange(updated);
27
+ };
28
+
29
+ const incrementHours = () => {
30
+ const newHours = (hours + 1) % 24;
31
+ updateTime(newHours, minutes);
32
+ };
33
+
34
+ const decrementHours = () => {
35
+ const newHours = (hours - 1 + 24) % 24;
36
+ updateTime(newHours, minutes);
37
+ };
38
+
39
+ const incrementMinutes = () => {
40
+ const newMinutes = (minutes + minuteStep) % 60;
41
+ const hourChange = minutes + minuteStep >= 60 ? 1 : 0;
42
+ updateTime((hours + hourChange) % 24, newMinutes);
43
+ };
44
+
45
+ const decrementMinutes = () => {
46
+ const newMinutes = (minutes - minuteStep + 60) % 60;
47
+ const hourChange = minutes - minuteStep < 0 ? -1 : 0;
48
+ updateTime((hours + hourChange + 24) % 24, newMinutes);
49
+ };
50
+
51
+ const togglePeriod = () => {
52
+ const newHours = isPM ? hours - 12 : hours + 12;
53
+ updateTime(newHours, minutes);
54
+ };
55
+
56
+ return (
57
+ <View style={[datePickerStyles.timePicker, style]}>
58
+ <View style={datePickerStyles.timeColumns}>
59
+ {/* Hours column */}
60
+ <View style={datePickerStyles.timeColumn}>
61
+ <Button type="text" size="sm" onPress={incrementHours} disabled={disabled}>
62
+ <Icon name="chevron-up" size={20} />
63
+ </Button>
64
+ <Text typography="h3" weight="semibold">
65
+ {String(displayHours).padStart(2, '0')}
66
+ </Text>
67
+ <Button type="text" size="sm" onPress={decrementHours} disabled={disabled}>
68
+ <Icon name="chevron-down" size={20} />
69
+ </Button>
70
+ </View>
71
+
72
+ {/* Separator */}
73
+ <View style={datePickerStyles.timeSeparator}>
74
+ <Text typography="h3" weight="semibold">:</Text>
75
+ </View>
76
+
77
+ {/* Minutes column */}
78
+ <View style={datePickerStyles.timeColumn}>
79
+ <Button type="text" size="sm" onPress={incrementMinutes} disabled={disabled}>
80
+ <Icon name="chevron-up" size={20} />
81
+ </Button>
82
+ <Text typography="h3" weight="semibold">
83
+ {String(minutes).padStart(2, '0')}
84
+ </Text>
85
+ <Button type="text" size="sm" onPress={decrementMinutes} disabled={disabled}>
86
+ <Icon name="chevron-down" size={20} />
87
+ </Button>
88
+ </View>
89
+
90
+ {/* AM/PM toggle for 12-hour mode */}
91
+ {is12Hour && (
92
+ <View style={datePickerStyles.timeColumn}>
93
+ <Button
94
+ type="outlined"
95
+ size="sm"
96
+ onPress={togglePeriod}
97
+ disabled={disabled}
98
+ >
99
+ {isPM ? 'PM' : 'AM'}
100
+ </Button>
101
+ </View>
102
+ )}
103
+ </View>
104
+ </View>
105
+ );
106
+ };