@idealyst/datepicker 1.0.0 → 1.0.58

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 (45) hide show
  1. package/package.json +10 -5
  2. package/src/DateInput/DateInput.native.tsx +80 -0
  3. package/src/DateInput/DateInput.styles.tsx +118 -0
  4. package/src/DateInput/DateInput.web.tsx +79 -0
  5. package/src/DateInput/DateInputBase.tsx +233 -0
  6. package/src/DateInput/index.native.ts +2 -0
  7. package/src/DateInput/index.ts +2 -0
  8. package/src/DateInput/types.ts +60 -0
  9. package/src/DatePicker/Calendar.native.tsx +180 -78
  10. package/src/DatePicker/Calendar.styles.tsx +73 -70
  11. package/src/DatePicker/DatePicker.native.tsx +24 -6
  12. package/src/DatePicker/DatePicker.styles.tsx +18 -11
  13. package/src/DatePicker/DatePicker.web.tsx +1 -1
  14. package/src/DatePicker/index.ts +1 -1
  15. package/src/DateRangePicker/RangeCalendar.native.tsx +143 -55
  16. package/src/DateRangePicker/RangeCalendar.styles.tsx +65 -39
  17. package/src/DateRangePicker/RangeCalendar.web.tsx +169 -60
  18. package/src/DateRangePicker/types.ts +9 -0
  19. package/src/DateTimePicker/DateTimePicker.native.tsx +11 -69
  20. package/src/DateTimePicker/DateTimePicker.tsx +12 -70
  21. package/src/DateTimePicker/DateTimePickerBase.tsx +204 -0
  22. package/src/DateTimePicker/TimePicker.native.tsx +9 -196
  23. package/src/DateTimePicker/TimePicker.styles.tsx +4 -2
  24. package/src/DateTimePicker/TimePicker.tsx +9 -401
  25. package/src/DateTimePicker/TimePickerBase.tsx +232 -0
  26. package/src/DateTimePicker/primitives/ClockFace.native.tsx +195 -0
  27. package/src/DateTimePicker/primitives/ClockFace.web.tsx +168 -0
  28. package/src/DateTimePicker/primitives/TimeInput.native.tsx +53 -0
  29. package/src/DateTimePicker/primitives/TimeInput.web.tsx +66 -0
  30. package/src/DateTimePicker/primitives/index.native.ts +2 -0
  31. package/src/DateTimePicker/primitives/index.ts +2 -0
  32. package/src/DateTimePicker/primitives/index.web.ts +2 -0
  33. package/src/DateTimePicker/types.ts +0 -4
  34. package/src/DateTimePicker/utils/dimensions.native.ts +9 -0
  35. package/src/DateTimePicker/utils/dimensions.ts +9 -0
  36. package/src/DateTimePicker/utils/dimensions.web.ts +33 -0
  37. package/src/DateTimeRangePicker/DateTimeRangePicker.native.tsx +10 -199
  38. package/src/DateTimeRangePicker/DateTimeRangePicker.styles.tsx +3 -0
  39. package/src/DateTimeRangePicker/DateTimeRangePicker.web.tsx +11 -131
  40. package/src/DateTimeRangePicker/DateTimeRangePickerBase.tsx +337 -0
  41. package/src/DateTimeRangePicker/types.ts +0 -2
  42. package/src/examples/DatePickerExamples.tsx +42 -118
  43. package/src/index.native.ts +4 -0
  44. package/src/index.ts +4 -0
  45. /package/src/DatePicker/{Calendar.tsx → Calendar.web.tsx} +0 -0
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "@idealyst/datepicker",
3
- "version": "1.0.0",
3
+ "version": "1.0.58",
4
4
  "description": "Cross-platform date and time picker components for React and React Native",
5
- "documentation": "https://github.com/your-username/idealyst-framework/tree/main/packages/datepicker#readme",
5
+ "documentation": "https://github.com/IdealystIO/idealyst-framework/tree/main/packages/datepicker#readme",
6
+ "readme": "README.md",
6
7
  "main": "src/index.ts",
7
8
  "module": "src/index.ts",
8
9
  "types": "src/index.ts",
9
10
  "react-native": "src/index.native.ts",
10
11
  "repository": {
11
12
  "type": "git",
12
- "url": "https://github.com/your-username/idealyst-framework.git",
13
+ "url": "https://github.com/IdealystIO/idealyst-framework.git",
13
14
  "directory": "packages/datepicker"
14
15
  },
15
16
  "author": "Your Name <your.email@example.com>",
@@ -35,10 +36,11 @@
35
36
  "publish:npm": "npm publish"
36
37
  },
37
38
  "peerDependencies": {
38
- "@idealyst/components": "^1.0.40",
39
- "@idealyst/theme": "^1.0.40",
39
+ "@idealyst/components": "^1.0.58",
40
+ "@idealyst/theme": "^1.0.58",
40
41
  "react": ">=16.8.0",
41
42
  "react-native": ">=0.60.0",
43
+ "react-native-svg": ">=13.0.0",
42
44
  "react-native-unistyles": "^3.0.4"
43
45
  },
44
46
  "peerDependenciesMeta": {
@@ -51,6 +53,9 @@
51
53
  "react-native": {
52
54
  "optional": true
53
55
  },
56
+ "react-native-svg": {
57
+ "optional": true
58
+ },
54
59
  "react-native-unistyles": {
55
60
  "optional": true
56
61
  }
@@ -0,0 +1,80 @@
1
+ import React from 'react';
2
+ import { View, TextInput, Text } from '@idealyst/components';
3
+ import { DateInputBase } from './DateInputBase';
4
+ import { DateInputProps } from './types';
5
+ import { dateInputStyles } from './DateInput.styles';
6
+
7
+ export const DateInput: React.FC<DateInputProps> = (props) => {
8
+ const {
9
+ label,
10
+ error,
11
+ helperText,
12
+ size = 'medium',
13
+ variant = 'outlined',
14
+ disabled = false,
15
+ style,
16
+ inputStyle,
17
+ testID,
18
+ ...baseProps
19
+ } = props;
20
+
21
+ // Initialize styles
22
+ dateInputStyles.useVariants({
23
+ size,
24
+ variant,
25
+ state: disabled ? 'disabled' : error ? 'error' : undefined,
26
+ });
27
+
28
+ return (
29
+ <View style={style} testID={testID}>
30
+ {label && (
31
+ <Text style={dateInputStyles.label} testID={testID ? `${testID}-label` : undefined}>
32
+ {label}
33
+ </Text>
34
+ )}
35
+
36
+ <DateInputBase
37
+ {...baseProps}
38
+ disabled={disabled}
39
+ testID={testID}
40
+ renderInput={({
41
+ value,
42
+ onChangeText,
43
+ onFocus,
44
+ onBlur,
45
+ placeholder,
46
+ editable,
47
+ style: inputStyleProp,
48
+ testID: inputTestID,
49
+ }) => (
50
+ <TextInput
51
+ value={value}
52
+ onChangeText={onChangeText}
53
+ onFocus={onFocus}
54
+ onBlur={onBlur}
55
+ placeholder={placeholder}
56
+ editable={editable}
57
+ style={[inputStyleProp, inputStyle]}
58
+ testID={inputTestID}
59
+ autoComplete="off"
60
+ autoCorrect={false}
61
+ spellCheck={false}
62
+ keyboardType="default"
63
+ />
64
+ )}
65
+ />
66
+
67
+ {error && (
68
+ <Text style={dateInputStyles.errorText} testID={testID ? `${testID}-error` : undefined}>
69
+ {error}
70
+ </Text>
71
+ )}
72
+
73
+ {!error && helperText && (
74
+ <Text style={dateInputStyles.helperText} testID={testID ? `${testID}-helper` : undefined}>
75
+ {helperText}
76
+ </Text>
77
+ )}
78
+ </View>
79
+ );
80
+ };
@@ -0,0 +1,118 @@
1
+ import { createStyleSheet } from 'react-native-unistyles';
2
+
3
+ export const dateInputStyles = createStyleSheet((theme) => ({
4
+ container: {
5
+ width: '100%',
6
+ },
7
+
8
+ input: {
9
+ borderWidth: 1,
10
+ borderColor: theme.colors.border,
11
+ borderRadius: theme.radius.medium,
12
+ paddingHorizontal: theme.spacing.medium,
13
+ paddingVertical: theme.spacing.small,
14
+ fontSize: theme.typography.body.fontSize,
15
+ fontFamily: theme.typography.body.fontFamily,
16
+ color: theme.colors.text,
17
+ backgroundColor: theme.colors.background,
18
+
19
+ variants: {
20
+ size: {
21
+ small: {
22
+ paddingHorizontal: theme.spacing.small,
23
+ paddingVertical: theme.spacing.xs,
24
+ fontSize: theme.typography.caption.fontSize,
25
+ },
26
+ medium: {
27
+ paddingHorizontal: theme.spacing.medium,
28
+ paddingVertical: theme.spacing.small,
29
+ fontSize: theme.typography.body.fontSize,
30
+ },
31
+ large: {
32
+ paddingHorizontal: theme.spacing.large,
33
+ paddingVertical: theme.spacing.medium,
34
+ fontSize: theme.typography.subtitle.fontSize,
35
+ },
36
+ },
37
+
38
+ variant: {
39
+ outlined: {
40
+ borderWidth: 1,
41
+ backgroundColor: 'transparent',
42
+ },
43
+ filled: {
44
+ borderWidth: 0,
45
+ backgroundColor: theme.colors.surfaceVariant,
46
+ },
47
+ },
48
+
49
+ state: {
50
+ focused: {
51
+ borderColor: theme.colors.primary,
52
+ borderWidth: 2,
53
+ },
54
+ disabled: {
55
+ backgroundColor: theme.colors.disabled,
56
+ color: theme.colors.onDisabled,
57
+ borderColor: theme.colors.outline,
58
+ },
59
+ error: {
60
+ borderColor: theme.colors.error,
61
+ borderWidth: 2,
62
+ },
63
+ },
64
+ },
65
+
66
+ _web: {
67
+ outlineStyle: 'none',
68
+ cursor: 'text',
69
+
70
+ ':focus': {
71
+ borderColor: theme.colors.primary,
72
+ borderWidth: 2,
73
+ },
74
+
75
+ ':disabled': {
76
+ cursor: 'not-allowed',
77
+ },
78
+ },
79
+ },
80
+
81
+ inputError: {
82
+ borderColor: theme.colors.error,
83
+ borderWidth: 2,
84
+ },
85
+
86
+ inputFocused: {
87
+ borderColor: theme.colors.primary,
88
+ borderWidth: 2,
89
+ },
90
+
91
+ inputDisabled: {
92
+ backgroundColor: theme.colors.disabled,
93
+ color: theme.colors.onDisabled,
94
+ borderColor: theme.colors.outline,
95
+ },
96
+
97
+ label: {
98
+ fontSize: theme.typography.caption.fontSize,
99
+ fontFamily: theme.typography.caption.fontFamily,
100
+ color: theme.colors.onSurface,
101
+ marginBottom: theme.spacing.xs,
102
+ fontWeight: '500',
103
+ },
104
+
105
+ helperText: {
106
+ fontSize: theme.typography.caption.fontSize,
107
+ fontFamily: theme.typography.caption.fontFamily,
108
+ color: theme.colors.onSurfaceVariant,
109
+ marginTop: theme.spacing.xs,
110
+ },
111
+
112
+ errorText: {
113
+ fontSize: theme.typography.caption.fontSize,
114
+ fontFamily: theme.typography.caption.fontFamily,
115
+ color: theme.colors.error,
116
+ marginTop: theme.spacing.xs,
117
+ },
118
+ }));
@@ -0,0 +1,79 @@
1
+ import React from 'react';
2
+ import { View, TextInput, Text } from '@idealyst/components';
3
+ import { DateInputBase } from './DateInputBase';
4
+ import { DateInputProps } from './types';
5
+ import { dateInputStyles } from './DateInput.styles';
6
+
7
+ export const DateInput: React.FC<DateInputProps> = (props) => {
8
+ const {
9
+ label,
10
+ error,
11
+ helperText,
12
+ size = 'medium',
13
+ variant = 'outlined',
14
+ disabled = false,
15
+ style,
16
+ inputStyle,
17
+ testID,
18
+ ...baseProps
19
+ } = props;
20
+
21
+ // Initialize styles
22
+ dateInputStyles.useVariants({
23
+ size,
24
+ variant,
25
+ state: disabled ? 'disabled' : error ? 'error' : undefined,
26
+ });
27
+
28
+ return (
29
+ <View style={style} testID={testID}>
30
+ {label && (
31
+ <Text style={dateInputStyles.label} testID={testID ? `${testID}-label` : undefined}>
32
+ {label}
33
+ </Text>
34
+ )}
35
+
36
+ <DateInputBase
37
+ {...baseProps}
38
+ disabled={disabled}
39
+ testID={testID}
40
+ renderInput={({
41
+ value,
42
+ onChangeText,
43
+ onFocus,
44
+ onBlur,
45
+ placeholder,
46
+ editable,
47
+ style: inputStyleProp,
48
+ testID: inputTestID,
49
+ }) => (
50
+ <TextInput
51
+ value={value}
52
+ onChangeText={onChangeText}
53
+ onFocus={onFocus}
54
+ onBlur={onBlur}
55
+ placeholder={placeholder}
56
+ editable={editable}
57
+ style={[inputStyleProp, inputStyle]}
58
+ testID={inputTestID}
59
+ autoComplete="off"
60
+ autoCorrect={false}
61
+ spellCheck={false}
62
+ />
63
+ )}
64
+ />
65
+
66
+ {error && (
67
+ <Text style={dateInputStyles.errorText} testID={testID ? `${testID}-error` : undefined}>
68
+ {error}
69
+ </Text>
70
+ )}
71
+
72
+ {!error && helperText && (
73
+ <Text style={dateInputStyles.helperText} testID={testID ? `${testID}-helper` : undefined}>
74
+ {helperText}
75
+ </Text>
76
+ )}
77
+ </View>
78
+ );
79
+ };
@@ -0,0 +1,233 @@
1
+ import React, { useState, useCallback, useRef, useEffect } from 'react';
2
+ import { View, TextInput } from '@idealyst/components';
3
+ import { DateInputProps } from './types';
4
+ import { dateInputStyles } from './DateInput.styles';
5
+
6
+ interface DateInputBaseProps extends DateInputProps {
7
+ renderInput: (props: {
8
+ value: string;
9
+ onChangeText: (text: string) => void;
10
+ onFocus: () => void;
11
+ onBlur: () => void;
12
+ placeholder?: string;
13
+ editable: boolean;
14
+ style?: any;
15
+ testID?: string;
16
+ }) => React.ReactNode;
17
+ }
18
+
19
+ // Common date formats for parsing
20
+ const DEFAULT_INPUT_FORMATS = [
21
+ 'MM/dd/yyyy',
22
+ 'M/d/yyyy',
23
+ 'MM/dd/yy',
24
+ 'M/d/yy',
25
+ 'yyyy-MM-dd',
26
+ 'MM-dd-yyyy',
27
+ 'M-d-yyyy',
28
+ 'dd/MM/yyyy',
29
+ 'd/M/yyyy',
30
+ 'dd-MM-yyyy',
31
+ 'd-M-yyyy',
32
+ 'yyyy/MM/dd',
33
+ 'MMM dd, yyyy',
34
+ 'MMM d, yyyy',
35
+ 'MMMM dd, yyyy',
36
+ 'MMMM d, yyyy',
37
+ ];
38
+
39
+ const DEFAULT_DISPLAY_FORMAT = 'MMMM d, yyyy';
40
+
41
+ export const DateInputBase: React.FC<DateInputBaseProps> = ({
42
+ value,
43
+ onChange,
44
+ minDate,
45
+ maxDate,
46
+ disabled = false,
47
+ placeholder = 'Enter date...',
48
+ displayFormat = DEFAULT_DISPLAY_FORMAT,
49
+ inputFormats = DEFAULT_INPUT_FORMATS,
50
+ locale = 'en-US',
51
+ style,
52
+ testID,
53
+ onFocus,
54
+ onBlur,
55
+ renderInput,
56
+ }) => {
57
+ const [inputValue, setInputValue] = useState('');
58
+ const [isFocused, setIsFocused] = useState(false);
59
+ const [hasError, setHasError] = useState(false);
60
+ const inputRef = useRef<any>(null);
61
+
62
+ // Format date for display when not focused
63
+ const formatDateForDisplay = useCallback((date: Date) => {
64
+ try {
65
+ return date.toLocaleDateString(locale, {
66
+ year: 'numeric',
67
+ month: 'long',
68
+ day: 'numeric',
69
+ });
70
+ } catch {
71
+ return date.toLocaleDateString('en-US', {
72
+ year: 'numeric',
73
+ month: 'long',
74
+ day: 'numeric',
75
+ });
76
+ }
77
+ }, [locale]);
78
+
79
+ // Parse date from various input formats
80
+ const parseDate = useCallback((dateString: string): Date | null => {
81
+ if (!dateString.trim()) return null;
82
+
83
+ // Try direct Date parsing first
84
+ const directParse = new Date(dateString);
85
+ if (!isNaN(directParse.getTime())) {
86
+ return directParse;
87
+ }
88
+
89
+ // Try common formats
90
+ const trimmed = dateString.trim();
91
+
92
+ // Handle MM/dd/yyyy and variations
93
+ const slashFormats = [
94
+ /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/,
95
+ /^(\d{1,2})\/(\d{1,2})\/(\d{2})$/,
96
+ ];
97
+
98
+ for (const format of slashFormats) {
99
+ const match = trimmed.match(format);
100
+ if (match) {
101
+ const [, month, day, year] = match;
102
+ const fullYear = year.length === 2 ? 2000 + parseInt(year) : parseInt(year);
103
+ const date = new Date(fullYear, parseInt(month) - 1, parseInt(day));
104
+ if (!isNaN(date.getTime())) return date;
105
+ }
106
+ }
107
+
108
+ // Handle dd/MM/yyyy variations (European format)
109
+ const europeanMatch = trimmed.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
110
+ if (europeanMatch) {
111
+ const [, day, month, year] = europeanMatch;
112
+ const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
113
+ if (!isNaN(date.getTime())) return date;
114
+ }
115
+
116
+ // Handle yyyy-MM-dd (ISO format)
117
+ const isoMatch = trimmed.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
118
+ if (isoMatch) {
119
+ const [, year, month, day] = isoMatch;
120
+ const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
121
+ if (!isNaN(date.getTime())) return date;
122
+ }
123
+
124
+ // Handle dash formats
125
+ const dashFormats = [
126
+ /^(\d{1,2})-(\d{1,2})-(\d{4})$/,
127
+ /^(\d{4})-(\d{1,2})-(\d{1,2})$/,
128
+ ];
129
+
130
+ for (const format of dashFormats) {
131
+ const match = trimmed.match(format);
132
+ if (match) {
133
+ const [, first, second, third] = match;
134
+ let date: Date;
135
+
136
+ if (third.length === 4) {
137
+ // MM-dd-yyyy or dd-MM-yyyy
138
+ date = new Date(parseInt(third), parseInt(first) - 1, parseInt(second));
139
+ } else {
140
+ // yyyy-MM-dd
141
+ date = new Date(parseInt(first), parseInt(second) - 1, parseInt(third));
142
+ }
143
+
144
+ if (!isNaN(date.getTime())) return date;
145
+ }
146
+ }
147
+
148
+ return null;
149
+ }, []);
150
+
151
+ // Validate date against constraints
152
+ const validateDate = useCallback((date: Date): boolean => {
153
+ if (minDate && date < minDate) return false;
154
+ if (maxDate && date > maxDate) return false;
155
+ return true;
156
+ }, [minDate, maxDate]);
157
+
158
+ // Update input value when value prop changes
159
+ useEffect(() => {
160
+ if (isFocused) {
161
+ // When focused, keep the raw input value
162
+ return;
163
+ }
164
+
165
+ if (value) {
166
+ setInputValue(formatDateForDisplay(value));
167
+ setHasError(false);
168
+ } else {
169
+ setInputValue('');
170
+ setHasError(false);
171
+ }
172
+ }, [value, isFocused, formatDateForDisplay]);
173
+
174
+ const handleFocus = useCallback(() => {
175
+ setIsFocused(true);
176
+ // Switch to raw input format when focused
177
+ if (value) {
178
+ // Show in a common input format for editing
179
+ const editFormat = value.toLocaleDateString('en-US');
180
+ setInputValue(editFormat);
181
+ }
182
+ onFocus?.();
183
+ }, [value, onFocus]);
184
+
185
+ const handleBlur = useCallback(() => {
186
+ setIsFocused(false);
187
+
188
+ if (inputValue.trim()) {
189
+ const parsedDate = parseDate(inputValue);
190
+
191
+ if (parsedDate && validateDate(parsedDate)) {
192
+ onChange(parsedDate);
193
+ setHasError(false);
194
+ } else {
195
+ setHasError(true);
196
+ // Revert to previous valid value
197
+ if (value) {
198
+ setInputValue(formatDateForDisplay(value));
199
+ } else {
200
+ setInputValue('');
201
+ }
202
+ }
203
+ } else {
204
+ onChange(null);
205
+ setHasError(false);
206
+ }
207
+
208
+ onBlur?.();
209
+ }, [inputValue, parseDate, validateDate, onChange, value, formatDateForDisplay, onBlur]);
210
+
211
+ const handleChangeText = useCallback((text: string) => {
212
+ setInputValue(text);
213
+ setHasError(false);
214
+ }, []);
215
+
216
+ return (
217
+ <View style={[dateInputStyles.container, style]} testID={testID}>
218
+ {renderInput({
219
+ value: inputValue,
220
+ onChangeText: handleChangeText,
221
+ onFocus: handleFocus,
222
+ onBlur: handleBlur,
223
+ placeholder,
224
+ editable: !disabled,
225
+ style: [
226
+ dateInputStyles.input,
227
+ hasError && dateInputStyles.inputError,
228
+ ],
229
+ testID: testID ? `${testID}-input` : undefined,
230
+ })}
231
+ </View>
232
+ );
233
+ };
@@ -0,0 +1,2 @@
1
+ export { DateInput } from './DateInput.native';
2
+ export type { DateInputProps } from './types';
@@ -0,0 +1,2 @@
1
+ export { DateInput } from './DateInput.web';
2
+ export type { DateInputProps } from './types';
@@ -0,0 +1,60 @@
1
+ import { ViewStyle, TextStyle } from 'react-native';
2
+
3
+ export interface DateInputProps {
4
+ /** Current selected date */
5
+ value?: Date;
6
+
7
+ /** Called when date changes */
8
+ onChange: (date: Date | null) => void;
9
+
10
+ /** Minimum selectable date */
11
+ minDate?: Date;
12
+
13
+ /** Maximum selectable date */
14
+ maxDate?: Date;
15
+
16
+ /** Disabled state */
17
+ disabled?: boolean;
18
+
19
+ /** Placeholder text when no date is selected */
20
+ placeholder?: string;
21
+
22
+ /** Label for the input */
23
+ label?: string;
24
+
25
+ /** Error message to display */
26
+ error?: string;
27
+
28
+ /** Helper text */
29
+ helperText?: string;
30
+
31
+ /** Date format for display when not focused (default: 'MMMM d, yyyy') */
32
+ displayFormat?: string;
33
+
34
+ /** Accepted input formats for parsing (default includes common formats) */
35
+ inputFormats?: string[];
36
+
37
+ /** Locale for date formatting */
38
+ locale?: string;
39
+
40
+ /** Size variant */
41
+ size?: 'small' | 'medium' | 'large';
42
+
43
+ /** Visual variant */
44
+ variant?: 'outlined' | 'filled';
45
+
46
+ /** Custom styles */
47
+ style?: ViewStyle;
48
+
49
+ /** Custom text input styles */
50
+ inputStyle?: TextStyle;
51
+
52
+ /** Test ID for testing */
53
+ testID?: string;
54
+
55
+ /** Called when input is focused */
56
+ onFocus?: () => void;
57
+
58
+ /** Called when input is blurred */
59
+ onBlur?: () => void;
60
+ }