@purjayadi/react-native-datepicker 1.0.0 → 1.0.1

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
@@ -3,15 +3,15 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.0.0",
6
+ "version": "1.0.1",
7
7
  "description": "A flexible and customizable React Native datepicker component with bottom sheet modal",
8
8
  "main": "dist/index.js",
9
9
  "module": "dist/index.mjs",
10
10
  "types": "dist/index.d.ts",
11
11
  "files": [
12
12
  "dist",
13
- "src",
14
- "README.md"
13
+ "README.md",
14
+ "LICENSE"
15
15
  ],
16
16
  "scripts": {
17
17
  "build": "tsup",
@@ -1,582 +0,0 @@
1
- import React, {
2
- useState,
3
- useImperativeHandle,
4
- forwardRef,
5
- useCallback,
6
- useRef,
7
- useMemo,
8
- } from "react";
9
- import {
10
- View,
11
- TouchableOpacity,
12
- Platform,
13
- LayoutChangeEvent,
14
- Text,
15
- StyleSheet,
16
- ViewStyle,
17
- TextStyle,
18
- } from "react-native";
19
- import {
20
- format,
21
- parse,
22
- getDaysInMonth,
23
- subYears,
24
- isBefore,
25
- isAfter,
26
- getYear,
27
- startOfDay,
28
- } from "date-fns";
29
- import ScrollPicker from "react-native-wheel-scrollview-picker";
30
- import { BottomSheetModal, BottomSheetBackdrop } from "@gorhom/bottom-sheet";
31
- import { useSafeAreaInsets } from "react-native-safe-area-context";
32
-
33
- export interface DatePickerRef {
34
- show: (initialDate?: string) => void;
35
- }
36
-
37
- export interface DatePickerProps {
38
- value?: string;
39
- label?: string;
40
- placeholder?: string;
41
- onChange: (date: string) => void;
42
- minDate?: string;
43
- maxDate?: string;
44
- showTimePicker?: boolean;
45
- subText?: string | React.ReactNode;
46
- errors?: string[];
47
- // Styling props
48
- inputStyle?: ViewStyle;
49
- inputTextStyle?: TextStyle;
50
- labelStyle?: TextStyle;
51
- errorStyle?: TextStyle;
52
- subTextStyle?: TextStyle;
53
- highlightColor?: string;
54
- buttonStyle?: ViewStyle;
55
- buttonTextStyle?: TextStyle;
56
- cancelButtonStyle?: ViewStyle;
57
- cancelButtonTextStyle?: TextStyle;
58
- }
59
-
60
- const InternalButton: React.FC<{
61
- onPress: () => void;
62
- children: string;
63
- outline?: boolean;
64
- style?: ViewStyle;
65
- textStyle?: TextStyle;
66
- }> = ({ onPress, children, outline, style, textStyle }) => {
67
- return (
68
- <TouchableOpacity
69
- onPress={onPress}
70
- style={[styles.button, outline && styles.buttonOutline, style]}
71
- >
72
- <Text
73
- style={[
74
- styles.buttonText,
75
- outline && styles.buttonTextOutline,
76
- textStyle,
77
- ]}
78
- >
79
- {children}
80
- </Text>
81
- </TouchableOpacity>
82
- );
83
- };
84
-
85
- const DatePicker = forwardRef<DatePickerRef, DatePickerProps>(
86
- (
87
- {
88
- value,
89
- label,
90
- placeholder,
91
- onChange,
92
- minDate,
93
- maxDate,
94
- showTimePicker = false,
95
- subText,
96
- errors,
97
- inputStyle,
98
- inputTextStyle,
99
- labelStyle,
100
- errorStyle,
101
- subTextStyle,
102
- highlightColor = "#E5E5E5",
103
- buttonStyle,
104
- buttonTextStyle,
105
- cancelButtonStyle,
106
- cancelButtonTextStyle,
107
- },
108
- ref
109
- ) => {
110
- const nowYear = new Date().getFullYear();
111
- const bottomSheetRef = useRef<BottomSheetModal>(null);
112
- const { bottom: paddingBottom } = useSafeAreaInsets();
113
-
114
- const fullMonths = useMemo(
115
- () => [
116
- "January",
117
- "February",
118
- "March",
119
- "April",
120
- "May",
121
- "June",
122
- "July",
123
- "August",
124
- "September",
125
- "October",
126
- "November",
127
- "December",
128
- ],
129
- []
130
- );
131
-
132
- const shortMonths = useMemo(
133
- () => [
134
- "Jan",
135
- "Feb",
136
- "Mar",
137
- "Apr",
138
- "May",
139
- "Jun",
140
- "Jul",
141
- "Aug",
142
- "Sep",
143
- "Oct",
144
- "Nov",
145
- "Dec",
146
- ],
147
- []
148
- );
149
- const [day, setDay] = useState("01");
150
- const [month, setMonth] = useState("January");
151
- const [year, setYear] = useState(nowYear);
152
- const [days, setDays] = useState<string[]>([]);
153
- const [years, setYears] = useState<number[]>([]);
154
- const [initComplete, setInitComplete] = useState(false);
155
- const [hour, setHour] = useState("00");
156
- const [minute, setMinute] = useState("00");
157
- const [height, setHeight] = useState(180);
158
-
159
- const displayMonths = showTimePicker ? shortMonths : fullMonths;
160
-
161
- const show = useCallback(
162
- (initialDate?: string) => {
163
- const selectedDate = initialDate
164
- ? parse(initialDate, "yyyy-MM-dd", new Date())
165
- : new Date();
166
- const selectedDay = format(selectedDate, "dd");
167
- const currentMonthIndex = selectedDate.getMonth();
168
- const selectedMonth = fullMonths[currentMonthIndex];
169
- const selectedYear = getYear(selectedDate);
170
-
171
- const min = minDate
172
- ? parse(minDate, "yyyy-MM-dd", new Date())
173
- : subYears(new Date(), 200);
174
- const max = maxDate
175
- ? parse(maxDate, "yyyy-MM-dd", new Date())
176
- : new Date();
177
-
178
- const allYears = Array.from(
179
- { length: 200 },
180
- (_, i) => nowYear - i
181
- ).filter((y) => y >= getYear(min) && y <= getYear(max));
182
-
183
- const totalDays = getDaysInMonth(
184
- new Date(selectedYear, currentMonthIndex)
185
- );
186
- const daysArray = Array.from({ length: totalDays }, (_, i) =>
187
- `${i + 1}`.padStart(2, "0")
188
- );
189
-
190
- setDay(selectedDay);
191
- setMonth(selectedMonth);
192
- setYear(selectedYear);
193
- setYears(allYears);
194
- setDays(daysArray);
195
- setHour(format(selectedDate, "HH"));
196
- setMinute(format(selectedDate, "mm"));
197
- setInitComplete(true);
198
-
199
- setTimeout(() => bottomSheetRef.current?.present(), 10);
200
- },
201
- [minDate, maxDate, fullMonths]
202
- );
203
-
204
- useImperativeHandle(ref, () => ({ show }), [show]);
205
-
206
- const handleSetDate = () => {
207
- const monthIndex = fullMonths.findIndex((m) => m === month);
208
- const finalDateStr = `${year}-${(monthIndex + 1)
209
- .toString()
210
- .padStart(2, "0")}-${day}`;
211
-
212
- const finalDate = showTimePicker
213
- ? parse(
214
- `${finalDateStr} ${hour}:${minute}`,
215
- "yyyy-MM-dd HH:mm",
216
- new Date()
217
- )
218
- : parse(finalDateStr, "yyyy-MM-dd", new Date());
219
-
220
- const minDateTime = minDate
221
- ? startOfDay(parse(minDate, "yyyy-MM-dd", new Date()))
222
- : null;
223
- const maxDateTime = maxDate
224
- ? startOfDay(parse(maxDate, "yyyy-MM-dd", new Date()))
225
- : null;
226
-
227
- if (
228
- (minDateTime && isBefore(startOfDay(finalDate), minDateTime)) ||
229
- (maxDateTime && isAfter(startOfDay(finalDate), maxDateTime))
230
- ) {
231
- return;
232
- }
233
-
234
- const selectedMonthIndex = fullMonths.findIndex((m) => m === month);
235
- const formattedDate = showTimePicker
236
- ? `${day} ${shortMonths[selectedMonthIndex]} ${year} ${hour}:${minute}`
237
- : `${day} ${fullMonths[selectedMonthIndex]} ${year}`;
238
-
239
- onChange(formattedDate);
240
- bottomSheetRef.current?.close();
241
- };
242
-
243
- const handleMonthChange = (val: string) => {
244
- const fullMonth = showTimePicker
245
- ? (fullMonths.find((m) => m.startsWith(val)) ?? val)
246
- : val;
247
- const changedMonthIndex = fullMonths.findIndex((m) => m === fullMonth);
248
- const totalDays = getDaysInMonth(new Date(year, changedMonthIndex));
249
- const newDays = Array.from({ length: totalDays }, (_, i) =>
250
- `${i + 1}`.padStart(2, "0")
251
- );
252
- setMonth(fullMonth);
253
- setDays(newDays);
254
- };
255
-
256
- const handleYearChange = (val: number) => {
257
- const yearMonthIndex = fullMonths.findIndex((m) => m === month);
258
- const totalDays = getDaysInMonth(new Date(val, yearMonthIndex));
259
- const newDays = Array.from({ length: totalDays }, (_, i) =>
260
- `${i + 1}`.padStart(2, "0")
261
- );
262
- setYear(val);
263
- setDays(newDays);
264
- };
265
-
266
- const findSelectedDayByIndex = days.findIndex((val) => val === day);
267
- const findSelectedMonthByIndex = displayMonths.findIndex((val) =>
268
- showTimePicker ? month.startsWith(val) : val === month
269
- );
270
- const findSelectedYearByIndex = years.findIndex((val) => val === year);
271
- const hours = Array.from({ length: 24 }, (_, i) => `${i}`.padStart(2, "0"));
272
- const minutes = Array.from({ length: 60 }, (_, i) =>
273
- `${i}`.padStart(2, "0")
274
- );
275
-
276
- const dateValue = useMemo(() => {
277
- if (value) {
278
- return value;
279
- }
280
- return placeholder || "Select Date";
281
- }, [value, placeholder]);
282
-
283
- const handleLayout = (e?: LayoutChangeEvent) => {
284
- setHeight(e?.nativeEvent?.layout?.height || 180);
285
- };
286
-
287
- return (
288
- <>
289
- <View style={styles.container}>
290
- {label && <Text style={[styles.label, labelStyle]}>{label}</Text>}
291
- {subText && (
292
- <Text style={[styles.subText, subTextStyle]}>{subText}</Text>
293
- )}
294
- <TouchableOpacity
295
- style={[styles.input, errors && styles.inputError, inputStyle]}
296
- onPress={() => show(value)}
297
- >
298
- <Text
299
- style={[
300
- styles.inputText,
301
- !value && styles.inputTextPlaceholder,
302
- inputTextStyle,
303
- ]}
304
- >
305
- {dateValue}
306
- </Text>
307
- </TouchableOpacity>
308
- {errors && errors.length > 0 && (
309
- <Text style={[styles.errorText, errorStyle]}>{errors[0]}</Text>
310
- )}
311
- </View>
312
-
313
- <BottomSheetModal
314
- ref={bottomSheetRef}
315
- snapPoints={["50%"]}
316
- backgroundStyle={{ backgroundColor: "#fff" }}
317
- enableDynamicSizing={false}
318
- backdropComponent={(props) => (
319
- <BottomSheetBackdrop
320
- {...props}
321
- disappearsOnIndex={-1}
322
- appearsOnIndex={0}
323
- />
324
- )}
325
- enableContentPanningGesture={false}
326
- enableHandlePanningGesture={false}
327
- enablePanDownToClose={false}
328
- >
329
- {initComplete && (
330
- <View style={styles.modalContent}>
331
- <View>
332
- <Text style={styles.modalTitle}>
333
- Select Date {showTimePicker ? "& Time" : ""}
334
- </Text>
335
- </View>
336
- <View style={styles.pickerContainer} onLayout={handleLayout}>
337
- {/* Day */}
338
- <ScrollPicker
339
- dataSource={days}
340
- selectedIndex={findSelectedDayByIndex}
341
- renderItem={(data) => (
342
- <Text
343
- style={
344
- data === day ? styles.pickerItemBold : styles.pickerItem
345
- }
346
- >
347
- {data}
348
- </Text>
349
- )}
350
- onValueChange={(value) => setDay(value as string)}
351
- wrapperHeight={height}
352
- itemHeight={40}
353
- highlightBorderWidth={0.5}
354
- highlightColor={highlightColor}
355
- wrapperBackground="#FFFFFF"
356
- />
357
- {/* Month */}
358
- <ScrollPicker
359
- dataSource={displayMonths}
360
- selectedIndex={findSelectedMonthByIndex}
361
- renderItem={(data) => (
362
- <Text
363
- style={
364
- data === (showTimePicker ? month.slice(0, 3) : month)
365
- ? styles.pickerItemBold
366
- : styles.pickerItem
367
- }
368
- >
369
- {data}
370
- </Text>
371
- )}
372
- onValueChange={(value) => handleMonthChange(value as string)}
373
- wrapperHeight={height}
374
- itemHeight={40}
375
- highlightBorderWidth={0.5}
376
- highlightColor={highlightColor}
377
- wrapperBackground="#FFFFFF"
378
- />
379
- {/* Year */}
380
- <ScrollPicker
381
- dataSource={years}
382
- selectedIndex={findSelectedYearByIndex}
383
- renderItem={(data) => (
384
- <Text
385
- style={
386
- data === year
387
- ? styles.pickerItemBold
388
- : styles.pickerItem
389
- }
390
- >
391
- {data}
392
- </Text>
393
- )}
394
- onValueChange={(value) => handleYearChange(value as number)}
395
- wrapperHeight={height}
396
- itemHeight={40}
397
- highlightBorderWidth={0.5}
398
- highlightColor={highlightColor}
399
- wrapperBackground="#FFFFFF"
400
- />
401
- {showTimePicker && (
402
- <>
403
- <ScrollPicker
404
- dataSource={hours}
405
- selectedIndex={hours.findIndex((val) => val === hour)}
406
- renderItem={(data) => (
407
- <Text
408
- style={
409
- data === hour
410
- ? styles.pickerItemBold
411
- : styles.pickerItem
412
- }
413
- >
414
- {data}
415
- </Text>
416
- )}
417
- onValueChange={(value) => setHour(value as string)}
418
- wrapperHeight={height}
419
- itemHeight={40}
420
- highlightBorderWidth={0.5}
421
- highlightColor={highlightColor}
422
- wrapperBackground="#FFFFFF"
423
- />
424
- <ScrollPicker
425
- dataSource={minutes}
426
- selectedIndex={minutes.findIndex((val) => val === minute)}
427
- renderItem={(data) => (
428
- <Text
429
- style={
430
- data === minute
431
- ? styles.pickerItemBold
432
- : styles.pickerItem
433
- }
434
- >
435
- {data}
436
- </Text>
437
- )}
438
- onValueChange={(value) => setMinute(value as string)}
439
- wrapperHeight={height}
440
- itemHeight={40}
441
- highlightBorderWidth={0.5}
442
- highlightColor={highlightColor}
443
- wrapperBackground="#FFFFFF"
444
- />
445
- </>
446
- )}
447
- </View>
448
- <View
449
- style={[
450
- styles.buttonContainer,
451
- {
452
- paddingBottom: Platform.OS === "ios" ? paddingBottom : 20,
453
- },
454
- ]}
455
- >
456
- <InternalButton
457
- onPress={() => bottomSheetRef.current?.close()}
458
- outline
459
- style={cancelButtonStyle}
460
- textStyle={cancelButtonTextStyle}
461
- >
462
- Cancel
463
- </InternalButton>
464
- <InternalButton
465
- onPress={handleSetDate}
466
- style={buttonStyle}
467
- textStyle={buttonTextStyle}
468
- >
469
- Save
470
- </InternalButton>
471
- </View>
472
- </View>
473
- )}
474
- </BottomSheetModal>
475
- </>
476
- );
477
- }
478
- );
479
-
480
- const styles = StyleSheet.create({
481
- container: {
482
- flexDirection: "column",
483
- gap: 8,
484
- },
485
- label: {
486
- fontSize: 14,
487
- color: "#6B7280",
488
- },
489
- subText: {
490
- fontSize: 14,
491
- color: "#6B7280",
492
- width: "95%",
493
- },
494
- input: {
495
- fontSize: 16,
496
- color: "#000",
497
- backgroundColor: "#F3F4F6",
498
- borderRadius: 16,
499
- padding: 14,
500
- shadowColor: "#000",
501
- shadowOffset: {
502
- width: 0,
503
- height: 2,
504
- },
505
- shadowOpacity: 0.1,
506
- shadowRadius: 3,
507
- elevation: 2,
508
- },
509
- inputError: {
510
- borderWidth: 1,
511
- borderColor: "#EF4444",
512
- },
513
- inputText: {
514
- fontSize: 16,
515
- color: "#1F2937",
516
- },
517
- inputTextPlaceholder: {
518
- color: "#9CA3AF",
519
- },
520
- errorText: {
521
- fontSize: 12,
522
- color: "#EF4444",
523
- fontWeight: "bold",
524
- },
525
- modalContent: {
526
- flexDirection: "column",
527
- gap: 12,
528
- marginHorizontal: 24,
529
- flex: 1,
530
- },
531
- modalTitle: {
532
- fontSize: 20,
533
- fontWeight: "bold",
534
- color: "#1F2937",
535
- },
536
- pickerContainer: {
537
- flexDirection: "row",
538
- justifyContent: "space-between",
539
- paddingVertical: 16,
540
- flex: 1,
541
- },
542
- pickerItem: {
543
- fontSize: 16,
544
- color: "#1F2937",
545
- },
546
- pickerItemBold: {
547
- fontSize: 16,
548
- color: "#1F2937",
549
- fontWeight: "bold",
550
- },
551
- buttonContainer: {
552
- flexDirection: "row",
553
- justifyContent: "space-between",
554
- gap: 24,
555
- paddingVertical: 16,
556
- backgroundColor: "#fff",
557
- },
558
- button: {
559
- flex: 1,
560
- backgroundColor: "#3B82F6",
561
- borderRadius: 12,
562
- paddingVertical: 14,
563
- paddingHorizontal: 20,
564
- alignItems: "center",
565
- justifyContent: "center",
566
- },
567
- buttonOutline: {
568
- backgroundColor: "transparent",
569
- borderWidth: 1,
570
- borderColor: "#3B82F6",
571
- },
572
- buttonText: {
573
- color: "#fff",
574
- fontSize: 16,
575
- fontWeight: "600",
576
- },
577
- buttonTextOutline: {
578
- color: "#3B82F6",
579
- },
580
- });
581
-
582
- export default DatePicker;
package/src/index.tsx DELETED
@@ -1,2 +0,0 @@
1
- export { default as DatePicker } from "./DatePicker";
2
- export type { DatePickerRef, DatePickerProps } from "./DatePicker";