@hoddy-ui/core 2.5.47 → 2.5.49

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/next/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hoddy-ui/next",
3
- "version": "2.5.77",
3
+ "version": "2.5.81",
4
4
  "description": "Core rich react native components written in typescript, with support for expo-router",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -20,6 +20,7 @@
20
20
  "peerDependencies": {
21
21
  "@expo/vector-icons": ">=13.0.0",
22
22
  "@react-native-async-storage/async-storage": ">=1.18.1",
23
+ "@react-native-community/datetimepicker": "8.4.4",
23
24
  "@types/react": ">=18.2.6",
24
25
  "@types/react-native": ">=0.72.0",
25
26
  "expo-haptics": ">=12.4.0",
@@ -29,6 +30,7 @@
29
30
  "expo-system-ui": ">=2.2.1",
30
31
  "react": ">=18.2.0",
31
32
  "react-native": ">=0.71.8",
33
+ "react-native-modal-datetime-picker": "^15.0.0",
32
34
  "react-native-reanimated": ">=3.17.4",
33
35
  "react-native-safe-area-context": ">=4.5.3",
34
36
  "typescript": ">=5.0.4"
@@ -41,7 +43,7 @@
41
43
  "kinghoddy"
42
44
  ],
43
45
  "dependencies": {
44
- "react-native-size-matters": "^0.4.0"
46
+ "react-native-size-matters": "^0.4.2"
45
47
  },
46
48
  "devDependencies": {
47
49
  "tsup": "^7.2.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hoddy-ui/core",
3
- "version": "2.5.47",
3
+ "version": "2.5.49",
4
4
  "description": "Core rich react native components written in typescript",
5
5
  "main": "index.ts",
6
6
  "repository": {
@@ -14,6 +14,7 @@
14
14
  "peerDependencies": {
15
15
  "@expo/vector-icons": ">=13.0.0",
16
16
  "@react-native-async-storage/async-storage": ">=1.18.1",
17
+ "@react-native-community/datetimepicker": "8.4.4",
17
18
  "@react-navigation/native": ">=6.1.6",
18
19
  "@types/react": ">=18.2.6",
19
20
  "@types/react-native": ">=0.72.0",
@@ -23,6 +24,7 @@
23
24
  "expo-system-ui": ">=2.2.1",
24
25
  "react": ">=18.2.0",
25
26
  "react-native": ">=0.71.8",
27
+ "react-native-modal-datetime-picker": "^15.0.0",
26
28
  "react-native-reanimated": ">=3.17.4",
27
29
  "react-native-safe-area-context": ">=4.5.3",
28
30
  "typescript": ">=5.0.4"
@@ -39,6 +41,5 @@
39
41
  },
40
42
  "publishConfig": {
41
43
  "access": "public"
42
- },
43
- "devDependencies": {}
44
+ }
44
45
  }
@@ -102,6 +102,8 @@ const FlashMessage: React.FC = () => {
102
102
  };
103
103
  });
104
104
 
105
+ const textColor = type === "default" ? "#fff" : colors[type].text;
106
+
105
107
  const styles = ScaledSheet.create({
106
108
  root: {
107
109
  position: "absolute",
@@ -110,7 +112,7 @@ const FlashMessage: React.FC = () => {
110
112
  left: 0,
111
113
  paddingTop: top + 10,
112
114
  paddingHorizontal: "15@ms",
113
- backgroundColor: colors[type].main,
115
+ backgroundColor: type === "default" ? "#333" : colors[type].main,
114
116
  width: "100%",
115
117
  borderBottomLeftRadius: 10,
116
118
  borderBottomRightRadius: 10,
@@ -140,14 +142,12 @@ const FlashMessage: React.FC = () => {
140
142
  variant="h6"
141
143
  fontWeight={600}
142
144
  gutterBottom={3}
143
- style={{ color: "#fff" }}
145
+ color={textColor}
144
146
  >
145
147
  {message?.title}
146
148
  </Typography>
147
149
  )}
148
- <Typography style={{ color: "#fff" }}>
149
- {message?.message}
150
- </Typography>
150
+ <Typography color={textColor}>{message?.message}</Typography>
151
151
  </View>
152
152
  {/* <MaterialIcons color="#fff" size={36} name="error-outline" /> */}
153
153
  </View>
File without changes
@@ -7,6 +7,7 @@ import {
7
7
  TouchableOpacity,
8
8
  View,
9
9
  } from "react-native";
10
+ import DateTimePickerModal from "react-native-modal-datetime-picker";
10
11
  import {
11
12
  ScaledSheet,
12
13
  moderateScale,
@@ -26,6 +27,7 @@ const TextField: React.FC<TextFieldProps> = ({
26
27
  color = "primary",
27
28
  value,
28
29
  type,
30
+ placeholder = "",
29
31
  helperText,
30
32
  onChangeText,
31
33
  onSubmitEditing = () => {},
@@ -46,12 +48,14 @@ const TextField: React.FC<TextFieldProps> = ({
46
48
  }) => {
47
49
  const colors = useColors();
48
50
  const [focused, setFocused] = useState(false);
51
+ const [datePickerVisible, setDatePickerVisible] = useState(false);
52
+ const isDate = type === "date";
49
53
  const height =
50
54
  moderateScale(variant === "text" ? 50 : 45) *
51
55
  (size === "large" ? 1.2 : size === "small" ? 0.8 : 1);
52
56
 
53
57
  const labelAnim = useRef(
54
- new Animated.Value(height / moderateScale(variant === "text" ? 2.5 : 3.2))
58
+ new Animated.Value(height / moderateScale(variant === "text" ? 2.5 : 3.2)),
55
59
  ).current;
56
60
 
57
61
  React.useEffect(() => {
@@ -83,14 +87,14 @@ const TextField: React.FC<TextFieldProps> = ({
83
87
  variant === "outlined" || variant === "text"
84
88
  ? "#fff0"
85
89
  : focused
86
- ? colors.white[3]
87
- : colors.white[4],
90
+ ? colors.white[3]
91
+ : colors.white[4],
88
92
  flexDirection: "row",
89
93
  borderColor: error
90
94
  ? colors.error.main
91
95
  : focused
92
- ? colors[color].main
93
- : colors.textSecondary.main,
96
+ ? colors[color].main
97
+ : colors.textSecondary.main,
94
98
  borderWidth: error ? 1 : variant === "outlined" ? (focused ? 2 : 0.5) : 0,
95
99
  borderBottomWidth: variant === "text" ? 0.5 : undefined,
96
100
  width: "100%",
@@ -116,6 +120,21 @@ const TextField: React.FC<TextFieldProps> = ({
116
120
  paddingLeft: variant === "text" ? 0 : moderateScale(15),
117
121
  paddingTop: "13@ms",
118
122
  },
123
+ dateContent: {
124
+ flexDirection: "row",
125
+ alignItems: "center",
126
+ flex: 1,
127
+ paddingLeft: variant === "text" ? 0 : moderateScale(15),
128
+ paddingRight: moderateScale(10),
129
+ paddingTop: variant === "text" ? ms(13) : ms(12),
130
+ },
131
+ dateText: {
132
+ fontSize: "14@ms",
133
+ flex: 1,
134
+ },
135
+ datePlaceholder: {
136
+ color: colors.textSecondary.main,
137
+ },
119
138
  label: {
120
139
  fontFamily: getFontFamily(400),
121
140
  position: "absolute",
@@ -150,33 +169,67 @@ const TextField: React.FC<TextFieldProps> = ({
150
169
  autoCompleteType: "email",
151
170
  }
152
171
  : type === "number"
153
- ? {
154
- keyboardType: "numeric",
155
- }
156
- : type === "tel"
157
- ? {
158
- textContentType: "telephoneNumber",
159
- keyboardType: "phone-pad",
160
- }
161
- : type === "search"
162
- ? {
163
- keyboardType: "web-search",
164
- returnKeyType: "search",
165
- autoCapitalize: "none",
166
- }
167
- : type === "password"
168
- ? {
169
- secureTextEntry: true,
170
- autoCompleteType: "password",
171
- autoCapitalize: "none",
172
- textContentType: "password",
173
- }
174
- : {};
172
+ ? {
173
+ keyboardType: "numeric",
174
+ }
175
+ : type === "tel"
176
+ ? {
177
+ textContentType: "telephoneNumber",
178
+ keyboardType: "phone-pad",
179
+ }
180
+ : type === "search"
181
+ ? {
182
+ keyboardType: "web-search",
183
+ returnKeyType: "search",
184
+ autoCapitalize: "none",
185
+ }
186
+ : type === "password"
187
+ ? {
188
+ secureTextEntry: true,
189
+ autoCompleteType: "password",
190
+ autoCapitalize: "none",
191
+ textContentType: "password",
192
+ }
193
+ : {};
194
+ const parseDateValue = () => {
195
+ if (!value) return new Date();
196
+ if (value instanceof Date) return value;
197
+ const parts = `${value}`.split("/");
198
+ if (parts.length === 3) {
199
+ const [day, month, year] = parts;
200
+ const parsed = new Date(
201
+ parseInt(year, 10),
202
+ parseInt(month, 10) - 1,
203
+ parseInt(day, 10),
204
+ );
205
+ if (!isNaN(parsed.getTime())) return parsed;
206
+ }
207
+ const fallback = new Date(value);
208
+ return isNaN(fallback.getTime()) ? new Date() : fallback;
209
+ };
210
+
211
+ const handleDateConfirm = (date: Date) => {
212
+ const day = date.getDate();
213
+ const month = date.getMonth() + 1;
214
+ const year = date.getFullYear();
215
+ const dateString = `${day}/${month}/${year}`;
216
+ onChangeText?.(dateString);
217
+ setDatePickerVisible(false);
218
+ };
219
+
220
+ const handleContainerPress = () => {
221
+ if (disabled) return;
222
+ setFocused(true);
223
+ if (isDate) {
224
+ onFocus();
225
+ setDatePickerVisible(true);
226
+ }
227
+ };
175
228
  return (
176
229
  <>
177
230
  <View style={styles.root}>
178
231
  <TouchableOpacity
179
- onPress={() => setFocused(true)}
232
+ onPress={handleContainerPress}
180
233
  style={styles.container}
181
234
  >
182
235
  <Animated.Text style={{ ...styles.label, top: labelAnim }}>
@@ -202,6 +255,27 @@ const TextField: React.FC<TextFieldProps> = ({
202
255
  {options.find((cur) => cur.value === value)?.label}
203
256
  </Typography>
204
257
  </View>
258
+ ) : isDate ? (
259
+ <View style={styles.dateContent}>
260
+ <Typography
261
+ style={[
262
+ styles.dateText,
263
+ !value ? styles.datePlaceholder : undefined,
264
+ ]}
265
+ color={value ? "dark" : "textSecondary"}
266
+ >
267
+ {value || placeholder}
268
+ </Typography>
269
+ <View style={{ marginLeft: 8 }}>
270
+ {end ?? (
271
+ <Ionicons
272
+ name="calendar-outline"
273
+ size={22}
274
+ color={colors.textSecondary.main}
275
+ />
276
+ )}
277
+ </View>
278
+ </View>
205
279
  ) : (
206
280
  <TextInput
207
281
  onFocus={() => {
@@ -279,6 +353,18 @@ const TextField: React.FC<TextFieldProps> = ({
279
353
  {...selectMenuProps}
280
354
  />
281
355
  )}
356
+ {isDate && (
357
+ <DateTimePickerModal
358
+ isVisible={datePickerVisible}
359
+ mode="date"
360
+ date={parseDateValue()}
361
+ onConfirm={handleDateConfirm}
362
+ onCancel={() => {
363
+ setDatePickerVisible(false);
364
+ setFocused(false);
365
+ }}
366
+ />
367
+ )}
282
368
  </>
283
369
  );
284
370
  };
@@ -292,6 +378,7 @@ export const TextField2 = React.forwardRef<TextInput, TextFieldProps>(
292
378
  color = "primary",
293
379
  value,
294
380
  type,
381
+ placeholder,
295
382
  helperText,
296
383
  onChangeText,
297
384
  onSubmitEditing = () => {},
@@ -304,21 +391,22 @@ export const TextField2 = React.forwardRef<TextInput, TextFieldProps>(
304
391
  style = {},
305
392
  inputStyles = {},
306
393
  gutterBottom = 8,
307
- placeholder,
308
394
  end,
309
395
  options,
310
396
  multiline,
311
397
  selectMenuProps,
312
398
  ...props
313
399
  },
314
- ref
400
+ ref,
315
401
  ) => {
316
402
  const colors = useColors();
317
403
  const [focused, _setFocused] = useState(false);
318
404
  const [showPassword, setShowPassword] = useState(false);
405
+ const [datePickerVisible, setDatePickerVisible] = useState(false);
406
+ const isDate = type === "date";
319
407
 
320
408
  const height = moderateScale(
321
- multiline ? 50 + (props.numberOfLines || 1) * 18 : 50
409
+ multiline ? 50 + (props.numberOfLines || 1) * 18 : 50,
322
410
  );
323
411
 
324
412
  const setFocused = (value: boolean) => {
@@ -344,8 +432,8 @@ export const TextField2 = React.forwardRef<TextInput, TextFieldProps>(
344
432
  borderColor: error
345
433
  ? colors.error.main
346
434
  : focused
347
- ? colors[color].main
348
- : colors.white[4],
435
+ ? colors[color].main
436
+ : colors.white[4],
349
437
  borderWidth: error ? 1 : focused ? 2 : 1,
350
438
  width: "100%",
351
439
  borderRadius: rounded ? 30 : 10,
@@ -373,6 +461,20 @@ export const TextField2 = React.forwardRef<TextInput, TextFieldProps>(
373
461
  color: colors.textSecondary.light,
374
462
  paddingLeft: moderateScale(10),
375
463
  },
464
+ dateContent: {
465
+ flexDirection: "row",
466
+ alignItems: "center",
467
+ flex: 1,
468
+ paddingHorizontal: moderateScale(10),
469
+ paddingTop: multiline ? 4 : 0,
470
+ },
471
+ dateText: {
472
+ fontSize: "14@ms",
473
+ flex: 1,
474
+ },
475
+ datePlaceholder: {
476
+ color: colors.textSecondary.light,
477
+ },
376
478
  label: {},
377
479
  helperText: {
378
480
  paddingHorizontal: "15@s",
@@ -400,28 +502,62 @@ export const TextField2 = React.forwardRef<TextInput, TextFieldProps>(
400
502
  autoCompleteType: "email",
401
503
  }
402
504
  : type === "number"
403
- ? {
404
- keyboardType: "numeric",
405
- }
406
- : type === "tel"
407
- ? {
408
- textContentType: "telephoneNumber",
409
- keyboardType: "phone-pad",
410
- }
411
- : type === "search"
412
- ? {
413
- keyboardType: "web-search",
414
- returnKeyType: "search",
415
- autoCapitalize: "none",
416
- }
417
- : type === "password"
418
- ? {
419
- secureTextEntry: !showPassword,
420
- autoCompleteType: "password",
421
- autoCapitalize: "none",
422
- textContentType: "password",
423
- }
424
- : {};
505
+ ? {
506
+ keyboardType: "numeric",
507
+ }
508
+ : type === "tel"
509
+ ? {
510
+ textContentType: "telephoneNumber",
511
+ keyboardType: "phone-pad",
512
+ }
513
+ : type === "search"
514
+ ? {
515
+ keyboardType: "web-search",
516
+ returnKeyType: "search",
517
+ autoCapitalize: "none",
518
+ }
519
+ : type === "password"
520
+ ? {
521
+ secureTextEntry: !showPassword,
522
+ autoCompleteType: "password",
523
+ autoCapitalize: "none",
524
+ textContentType: "password",
525
+ }
526
+ : {};
527
+ const parseDateValue = () => {
528
+ if (!value) return new Date();
529
+ if (value instanceof Date) return value;
530
+ const parts = `${value}`.split("/");
531
+ if (parts.length === 3) {
532
+ const [day, month, year] = parts;
533
+ const parsed = new Date(
534
+ parseInt(year, 10),
535
+ parseInt(month, 10) - 1,
536
+ parseInt(day, 10),
537
+ );
538
+ if (!isNaN(parsed.getTime())) return parsed;
539
+ }
540
+ const fallback = new Date(value);
541
+ return isNaN(fallback.getTime()) ? new Date() : fallback;
542
+ };
543
+
544
+ const handleDateConfirm = (date: Date) => {
545
+ const day = date.getDate();
546
+ const month = date.getMonth() + 1;
547
+ const year = date.getFullYear();
548
+ const dateString = `${day}/${month}/${year}`;
549
+ onChangeText?.(dateString);
550
+ setDatePickerVisible(false);
551
+ };
552
+
553
+ const handleContainerPress = () => {
554
+ if (disabled) return;
555
+ setFocused(true);
556
+ if (isDate) {
557
+ onFocus();
558
+ setDatePickerVisible(true);
559
+ }
560
+ };
425
561
  return (
426
562
  <>
427
563
  <View style={styles.root}>
@@ -436,7 +572,7 @@ export const TextField2 = React.forwardRef<TextInput, TextFieldProps>(
436
572
  </Typography>
437
573
  )}
438
574
  <TouchableOpacity
439
- onPress={() => setFocused(true)}
575
+ onPress={handleContainerPress}
440
576
  style={styles.container}
441
577
  >
442
578
  <View style={{ marginTop: multiline ? 5 : 0 }}>{start}</View>
@@ -459,6 +595,27 @@ export const TextField2 = React.forwardRef<TextInput, TextFieldProps>(
459
595
  color={colors.dark.light}
460
596
  />
461
597
  </>
598
+ ) : isDate ? (
599
+ <View style={styles.dateContent}>
600
+ <Typography
601
+ style={[
602
+ styles.dateText,
603
+ !value ? styles.datePlaceholder : undefined,
604
+ ]}
605
+ color={value ? "dark" : "textSecondary"}
606
+ >
607
+ {value || placeholder}
608
+ </Typography>
609
+ <View style={{ marginLeft: 8 }}>
610
+ {end ?? (
611
+ <Ionicons
612
+ name="calendar-outline"
613
+ size={22}
614
+ color={colors.textSecondary.main}
615
+ />
616
+ )}
617
+ </View>
618
+ </View>
462
619
  ) : (
463
620
  <TextInput
464
621
  ref={ref}
@@ -522,6 +679,18 @@ export const TextField2 = React.forwardRef<TextInput, TextFieldProps>(
522
679
  </View>
523
680
  )}
524
681
  </View>
682
+ {isDate && (
683
+ <DateTimePickerModal
684
+ isVisible={datePickerVisible}
685
+ mode="date"
686
+ date={parseDateValue()}
687
+ onConfirm={handleDateConfirm}
688
+ onCancel={() => {
689
+ setDatePickerVisible(false);
690
+ setFocused(false);
691
+ }}
692
+ />
693
+ )}
525
694
  {options && (
526
695
  <SelectMenu
527
696
  options={options}
@@ -536,7 +705,7 @@ export const TextField2 = React.forwardRef<TextInput, TextFieldProps>(
536
705
  )}
537
706
  </>
538
707
  );
539
- }
708
+ },
540
709
  );
541
710
 
542
711
  export default TextField;
package/src/types.ts CHANGED
@@ -113,7 +113,7 @@ export interface FlashMessageProps {
113
113
  title?: string;
114
114
  actions?: Array<{ title: string; onPress?: () => void }>;
115
115
  duration?: number;
116
- type?: "success" | "warning" | "error";
116
+ type?: "success" | "warning" | "error" | "info" | "default";
117
117
  }
118
118
  export interface LinkButtonProps {
119
119
  title: string;
@@ -236,7 +236,14 @@ export interface TextFieldProps extends TextInputProps {
236
236
  variant?: "outlined" | "text" | "contained";
237
237
  color?: colorTypes;
238
238
  size?: "small" | "normal" | "large";
239
- type?: "email" | "tel" | "password" | "text" | "number" | "search";
239
+ type?:
240
+ | "email"
241
+ | "tel"
242
+ | "password"
243
+ | "text"
244
+ | "number"
245
+ | "search"
246
+ | "date";
240
247
  helperText?: string;
241
248
  value: any;
242
249
  start?: ReactNode;
@@ -265,15 +272,15 @@ export interface TypographyProps extends TextProps {
265
272
  textCase?: "capitalize" | "uppercase" | "lowercase" | undefined;
266
273
  lineHeight?: number;
267
274
  variant?:
268
- | "caption"
269
- | "body1"
270
- | "body2"
271
- | "h6"
272
- | "h5"
273
- | "h4"
274
- | "h3"
275
- | "h2"
276
- | "h1";
275
+ | "caption"
276
+ | "body1"
277
+ | "body2"
278
+ | "h6"
279
+ | "h5"
280
+ | "h4"
281
+ | "h3"
282
+ | "h2"
283
+ | "h1";
277
284
  align?: "center" | "left" | "right";
278
285
  gutterBottom?: number;
279
286
  numberOfLines?: number;
@@ -352,36 +359,36 @@ interface BaseAnimatorProps {
352
359
  // Type-specific animation props using discriminated unions
353
360
  export type AnimatorProps =
354
361
  | (BaseAnimatorProps & {
355
- type: "fade";
356
- // No additional props for fade animation
357
- })
362
+ type: "fade";
363
+ // No additional props for fade animation
364
+ })
358
365
  | (BaseAnimatorProps & {
359
- type: "grow";
360
- initialScale?: number;
361
- })
366
+ type: "grow";
367
+ initialScale?: number;
368
+ })
362
369
  | (BaseAnimatorProps & {
363
- type: "slide";
364
- direction?: "up" | "down" | "left" | "right";
365
- initialValue?: number;
366
- })
370
+ type: "slide";
371
+ direction?: "up" | "down" | "left" | "right";
372
+ initialValue?: number;
373
+ })
367
374
  | (BaseAnimatorProps & {
368
- type: "blink";
369
- blinkDuration?: number;
370
- minOpacity?: number;
371
- maxOpacity?: number;
372
- })
375
+ type: "blink";
376
+ blinkDuration?: number;
377
+ minOpacity?: number;
378
+ maxOpacity?: number;
379
+ })
373
380
  | (BaseAnimatorProps & {
374
- type: "float";
375
- closeDuration?: number;
376
- floatDistance?: number;
377
- floatDuration?: number;
378
- })
381
+ type: "float";
382
+ closeDuration?: number;
383
+ floatDistance?: number;
384
+ floatDuration?: number;
385
+ })
379
386
  | (BaseAnimatorProps & {
380
- type: "roll";
381
- initialTranslateY?: number;
382
- initialRotate?: string;
383
- })
387
+ type: "roll";
388
+ initialTranslateY?: number;
389
+ initialRotate?: string;
390
+ })
384
391
  | (BaseAnimatorProps & {
385
- type: "thrownup";
386
- // No additional props for thrownup animation
387
- });
392
+ type: "thrownup";
393
+ // No additional props for thrownup animation
394
+ });