react-native-month-day-picker 0.1.0
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/LICENSE +21 -0
- package/README.md +269 -0
- package/lib/commonjs/BirthdayPicker.js +177 -0
- package/lib/commonjs/BirthdayPicker.js.map +1 -0
- package/lib/commonjs/BirthdayPickerModal.js +176 -0
- package/lib/commonjs/BirthdayPickerModal.js.map +1 -0
- package/lib/commonjs/constants.js +80 -0
- package/lib/commonjs/constants.js.map +1 -0
- package/lib/commonjs/hooks/useBirthdayPicker.js +90 -0
- package/lib/commonjs/hooks/useBirthdayPicker.js.map +1 -0
- package/lib/commonjs/index.js +89 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/types.js +6 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/utils/dateUtils.js +103 -0
- package/lib/commonjs/utils/dateUtils.js.map +1 -0
- package/lib/commonjs/utils/localeUtils.js +90 -0
- package/lib/commonjs/utils/localeUtils.js.map +1 -0
- package/lib/module/BirthdayPicker.js +169 -0
- package/lib/module/BirthdayPicker.js.map +1 -0
- package/lib/module/BirthdayPickerModal.js +169 -0
- package/lib/module/BirthdayPickerModal.js.map +1 -0
- package/lib/module/constants.js +74 -0
- package/lib/module/constants.js.map +1 -0
- package/lib/module/hooks/useBirthdayPicker.js +84 -0
- package/lib/module/hooks/useBirthdayPicker.js.map +1 -0
- package/lib/module/index.js +16 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils/dateUtils.js +92 -0
- package/lib/module/utils/dateUtils.js.map +1 -0
- package/lib/module/utils/localeUtils.js +81 -0
- package/lib/module/utils/localeUtils.js.map +1 -0
- package/lib/typescript/BirthdayPicker.d.ts +25 -0
- package/lib/typescript/BirthdayPicker.d.ts.map +1 -0
- package/lib/typescript/BirthdayPickerModal.d.ts +24 -0
- package/lib/typescript/BirthdayPickerModal.d.ts.map +1 -0
- package/lib/typescript/constants.d.ts +39 -0
- package/lib/typescript/constants.d.ts.map +1 -0
- package/lib/typescript/hooks/useBirthdayPicker.d.ts +17 -0
- package/lib/typescript/hooks/useBirthdayPicker.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +8 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +160 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/lib/typescript/utils/dateUtils.d.ts +43 -0
- package/lib/typescript/utils/dateUtils.d.ts.map +1 -0
- package/lib/typescript/utils/localeUtils.d.ts +28 -0
- package/lib/typescript/utils/localeUtils.d.ts.map +1 -0
- package/package.json +137 -0
- package/src/BirthdayPicker.tsx +210 -0
- package/src/BirthdayPickerModal.tsx +192 -0
- package/src/constants.ts +64 -0
- package/src/hooks/useBirthdayPicker.ts +106 -0
- package/src/index.ts +31 -0
- package/src/types.ts +189 -0
- package/src/utils/dateUtils.ts +101 -0
- package/src/utils/localeUtils.ts +99 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { UseBirthdayPickerOptions, UseBirthdayPickerReturn } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Hook for managing birthday picker state
|
|
4
|
+
*
|
|
5
|
+
* @param options - Configuration options
|
|
6
|
+
* @returns State and handlers for birthday picker
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const { value, setMonth, setDay, daysInMonth } = useBirthdayPicker({
|
|
11
|
+
* initialValue: { month: 6, day: 15 },
|
|
12
|
+
* });
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function useBirthdayPicker(options?: UseBirthdayPickerOptions): UseBirthdayPickerReturn;
|
|
16
|
+
export default useBirthdayPicker;
|
|
17
|
+
//# sourceMappingURL=useBirthdayPicker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBirthdayPicker.d.ts","sourceRoot":"","sources":["../../../src/hooks/useBirthdayPicker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,UAAU,CAAC;AASlB;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE,wBAA6B,GACrC,uBAAuB,CA0EzB;AAED,eAAe,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { BirthdayPicker } from './BirthdayPicker';
|
|
2
|
+
export { BirthdayPickerModal } from './BirthdayPickerModal';
|
|
3
|
+
export { useBirthdayPicker } from './hooks/useBirthdayPicker';
|
|
4
|
+
export type { BirthdayValue, MonthFormat, BirthdayPickerProps, BirthdayPickerModalProps, UseBirthdayPickerOptions, UseBirthdayPickerReturn, } from './types';
|
|
5
|
+
export { getDaysInMonth, clampDay, isValidBirthday, getDaysArray, getMonthsArray, normalizeBirthday, } from './utils/dateUtils';
|
|
6
|
+
export { getMonthNames, formatMonth, formatDay } from './utils/localeUtils';
|
|
7
|
+
export { BirthdayPicker as default } from './BirthdayPicker';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAG5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAG9D,YAAY,EACV,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,cAAc,EACd,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,cAAc,EACd,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAG5E,OAAO,EAAE,cAAc,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { ViewStyle } from 'react-native';
|
|
2
|
+
/**
|
|
3
|
+
* Represents a month-day birthday value without year.
|
|
4
|
+
* Month is 1-indexed (1 = January, 12 = December)
|
|
5
|
+
* Day is 1-indexed (1-31 depending on month)
|
|
6
|
+
*/
|
|
7
|
+
export type BirthdayValue = {
|
|
8
|
+
month: number;
|
|
9
|
+
day: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Format for displaying month names
|
|
13
|
+
*/
|
|
14
|
+
export type MonthFormat = 'long' | 'short' | 'numeric';
|
|
15
|
+
/**
|
|
16
|
+
* Props for the BirthdayPicker component
|
|
17
|
+
*/
|
|
18
|
+
export interface BirthdayPickerProps {
|
|
19
|
+
/**
|
|
20
|
+
* Current value (controlled mode)
|
|
21
|
+
*/
|
|
22
|
+
value?: BirthdayValue;
|
|
23
|
+
/**
|
|
24
|
+
* Default value (uncontrolled mode)
|
|
25
|
+
*/
|
|
26
|
+
defaultValue?: BirthdayValue;
|
|
27
|
+
/**
|
|
28
|
+
* Called when value changes
|
|
29
|
+
*/
|
|
30
|
+
onChange?: (value: BirthdayValue) => void;
|
|
31
|
+
/**
|
|
32
|
+
* BCP 47 locale string for month name formatting
|
|
33
|
+
* @default 'en-US'
|
|
34
|
+
*/
|
|
35
|
+
locale?: string;
|
|
36
|
+
/**
|
|
37
|
+
* How to format month names
|
|
38
|
+
* @default 'long'
|
|
39
|
+
*/
|
|
40
|
+
monthFormat?: MonthFormat;
|
|
41
|
+
/**
|
|
42
|
+
* Whether to allow Feb 29 as a valid birthday
|
|
43
|
+
* @default true
|
|
44
|
+
*/
|
|
45
|
+
allowLeapDay?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Disable interaction
|
|
48
|
+
* @default false
|
|
49
|
+
*/
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Test ID for testing
|
|
53
|
+
*/
|
|
54
|
+
testID?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Container style
|
|
57
|
+
*/
|
|
58
|
+
style?: ViewStyle;
|
|
59
|
+
/**
|
|
60
|
+
* Height of each item in the wheel
|
|
61
|
+
* @default 40
|
|
62
|
+
*/
|
|
63
|
+
itemHeight?: number;
|
|
64
|
+
/**
|
|
65
|
+
* Number of visible items in each wheel (must be odd)
|
|
66
|
+
* @default 5
|
|
67
|
+
*/
|
|
68
|
+
visibleItems?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Accessibility label for month picker
|
|
71
|
+
* @default 'Month picker'
|
|
72
|
+
*/
|
|
73
|
+
monthAccessibilityLabel?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Accessibility label for day picker
|
|
76
|
+
* @default 'Day picker'
|
|
77
|
+
*/
|
|
78
|
+
dayAccessibilityLabel?: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Props for the BirthdayPickerModal component
|
|
82
|
+
*/
|
|
83
|
+
export interface BirthdayPickerModalProps extends BirthdayPickerProps {
|
|
84
|
+
/**
|
|
85
|
+
* Whether the modal is visible
|
|
86
|
+
*/
|
|
87
|
+
visible: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Called when user confirms selection
|
|
90
|
+
*/
|
|
91
|
+
onConfirm: (value: BirthdayValue) => void;
|
|
92
|
+
/**
|
|
93
|
+
* Called when user cancels
|
|
94
|
+
*/
|
|
95
|
+
onCancel: () => void;
|
|
96
|
+
/**
|
|
97
|
+
* Modal title
|
|
98
|
+
* @default 'Select Birthday'
|
|
99
|
+
*/
|
|
100
|
+
title?: string;
|
|
101
|
+
/**
|
|
102
|
+
* Confirm button text
|
|
103
|
+
* @default 'Confirm'
|
|
104
|
+
*/
|
|
105
|
+
confirmText?: string;
|
|
106
|
+
/**
|
|
107
|
+
* Cancel button text
|
|
108
|
+
* @default 'Cancel'
|
|
109
|
+
*/
|
|
110
|
+
cancelText?: string;
|
|
111
|
+
/**
|
|
112
|
+
* Animation type for modal presentation
|
|
113
|
+
* @default 'slide'
|
|
114
|
+
*/
|
|
115
|
+
animationType?: 'slide' | 'fade' | 'none';
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Options for useBirthdayPicker hook
|
|
119
|
+
*/
|
|
120
|
+
export interface UseBirthdayPickerOptions {
|
|
121
|
+
/**
|
|
122
|
+
* Initial value
|
|
123
|
+
*/
|
|
124
|
+
initialValue?: BirthdayValue;
|
|
125
|
+
/**
|
|
126
|
+
* Whether to allow Feb 29
|
|
127
|
+
* @default true
|
|
128
|
+
*/
|
|
129
|
+
allowLeapDay?: boolean;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Return type of useBirthdayPicker hook
|
|
133
|
+
*/
|
|
134
|
+
export interface UseBirthdayPickerReturn {
|
|
135
|
+
/**
|
|
136
|
+
* Current birthday value
|
|
137
|
+
*/
|
|
138
|
+
value: BirthdayValue;
|
|
139
|
+
/**
|
|
140
|
+
* Set the month (will clamp day if needed)
|
|
141
|
+
*/
|
|
142
|
+
setMonth: (month: number) => void;
|
|
143
|
+
/**
|
|
144
|
+
* Set the day
|
|
145
|
+
*/
|
|
146
|
+
setDay: (day: number) => void;
|
|
147
|
+
/**
|
|
148
|
+
* Set the entire value
|
|
149
|
+
*/
|
|
150
|
+
setValue: (value: BirthdayValue) => void;
|
|
151
|
+
/**
|
|
152
|
+
* Number of days in the currently selected month
|
|
153
|
+
*/
|
|
154
|
+
daysInMonth: number;
|
|
155
|
+
/**
|
|
156
|
+
* Whether the current value is valid
|
|
157
|
+
*/
|
|
158
|
+
isValid: boolean;
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAEvD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,KAAK,CAAC,EAAE,aAAa,CAAC;IAEtB;;OAEG;IACH,YAAY,CAAC,EAAE,aAAa,CAAC;IAE7B;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAE1C;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAE1B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAElB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAEjC;;;OAGG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,wBAAyB,SAAQ,mBAAmB;IACnE;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,SAAS,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAE1C;;OAEG;IACH,QAAQ,EAAE,MAAM,IAAI,CAAC;IAErB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;CAC3C;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,YAAY,CAAC,EAAE,aAAa,CAAC;IAE7B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,KAAK,EAAE,aAAa,CAAC;IAErB;;OAEG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAElC;;OAEG;IACH,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAE9B;;OAEG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAEzC;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { BirthdayValue } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Get the number of days in a given month
|
|
4
|
+
* @param month - Month (1-12)
|
|
5
|
+
* @param allowLeapDay - Whether to allow Feb 29
|
|
6
|
+
* @returns Number of days in the month
|
|
7
|
+
*/
|
|
8
|
+
export declare function getDaysInMonth(month: number, allowLeapDay?: boolean): number;
|
|
9
|
+
/**
|
|
10
|
+
* Clamp a day value to be valid for a given month
|
|
11
|
+
* @param day - Day to clamp
|
|
12
|
+
* @param month - Month (1-12)
|
|
13
|
+
* @param allowLeapDay - Whether to allow Feb 29
|
|
14
|
+
* @returns Clamped day value
|
|
15
|
+
*/
|
|
16
|
+
export declare function clampDay(day: number, month: number, allowLeapDay?: boolean): number;
|
|
17
|
+
/**
|
|
18
|
+
* Check if a birthday value is valid
|
|
19
|
+
* @param value - Birthday value to validate
|
|
20
|
+
* @param allowLeapDay - Whether to allow Feb 29
|
|
21
|
+
* @returns Whether the value is valid
|
|
22
|
+
*/
|
|
23
|
+
export declare function isValidBirthday(value: BirthdayValue, allowLeapDay?: boolean): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Generate an array of day numbers for a given month
|
|
26
|
+
* @param month - Month (1-12)
|
|
27
|
+
* @param allowLeapDay - Whether to allow Feb 29
|
|
28
|
+
* @returns Array of day numbers [1, 2, 3, ..., n]
|
|
29
|
+
*/
|
|
30
|
+
export declare function getDaysArray(month: number, allowLeapDay?: boolean): number[];
|
|
31
|
+
/**
|
|
32
|
+
* Generate an array of month numbers
|
|
33
|
+
* @returns Array of month numbers [1, 2, 3, ..., 12]
|
|
34
|
+
*/
|
|
35
|
+
export declare function getMonthsArray(): number[];
|
|
36
|
+
/**
|
|
37
|
+
* Normalize a birthday value to ensure it's valid
|
|
38
|
+
* @param value - Birthday value to normalize
|
|
39
|
+
* @param allowLeapDay - Whether to allow Feb 29
|
|
40
|
+
* @returns Normalized birthday value
|
|
41
|
+
*/
|
|
42
|
+
export declare function normalizeBirthday(value: BirthdayValue, allowLeapDay?: boolean): BirthdayValue;
|
|
43
|
+
//# sourceMappingURL=dateUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dateUtils.d.ts","sourceRoot":"","sources":["../../../src/utils/dateUtils.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,UAAO,GAAG,MAAM,CAUzE;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,YAAY,UAAO,GAClB,MAAM,CAGR;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,YAAY,UAAO,GAClB,OAAO,CAeT;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,UAAO,GAAG,MAAM,EAAE,CAGzE;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAEzC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,aAAa,EACpB,YAAY,UAAO,GAClB,aAAa,CAIf"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { MonthFormat } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Get month names for a given locale and format
|
|
4
|
+
* @param locale - BCP 47 locale string (e.g., 'en-US', 'ja-JP')
|
|
5
|
+
* @param format - Format for month names ('long', 'short', 'numeric')
|
|
6
|
+
* @returns Array of month names/numbers (1-indexed, index 0 is empty string)
|
|
7
|
+
*/
|
|
8
|
+
export declare function getMonthNames(locale?: string, format?: MonthFormat): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Format a single month number to a localized string
|
|
11
|
+
* @param month - Month number (1-12)
|
|
12
|
+
* @param locale - BCP 47 locale string
|
|
13
|
+
* @param format - Format for month name
|
|
14
|
+
* @returns Formatted month name
|
|
15
|
+
*/
|
|
16
|
+
export declare function formatMonth(month: number, locale?: string, format?: MonthFormat): string;
|
|
17
|
+
/**
|
|
18
|
+
* Format a day number with optional locale-specific formatting
|
|
19
|
+
* @param day - Day number (1-31)
|
|
20
|
+
* @param locale - BCP 47 locale string (currently unused, for future i18n)
|
|
21
|
+
* @returns Formatted day string
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatDay(day: number, locale?: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Clear the month names cache (useful for testing)
|
|
26
|
+
*/
|
|
27
|
+
export declare function clearMonthNamesCache(): void;
|
|
28
|
+
//# sourceMappingURL=localeUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localeUtils.d.ts","sourceRoot":"","sources":["../../../src/utils/localeUtils.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAc5C;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,MAAM,GAAE,MAAuB,EAC/B,MAAM,GAAE,WAAkC,GACzC,MAAM,EAAE,CA8BV;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,MAAuB,EAC/B,MAAM,GAAE,WAAkC,GACzC,MAAM,CAOR;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,MAAuB,GAC9B,MAAM,CAER;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
|
package/package.json
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-month-day-picker",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A privacy-friendly birthday picker for React Native that uses only month and day (no year)",
|
|
5
|
+
"main": "lib/commonjs/index.js",
|
|
6
|
+
"module": "lib/module/index.js",
|
|
7
|
+
"types": "lib/typescript/index.d.ts",
|
|
8
|
+
"react-native": "src/index.ts",
|
|
9
|
+
"source": "src/index.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"lib",
|
|
13
|
+
"!**/__tests__",
|
|
14
|
+
"!**/__fixtures__",
|
|
15
|
+
"!**/__mocks__"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "bob build",
|
|
19
|
+
"clean": "del-cli lib",
|
|
20
|
+
"prepare": "node scripts/prepare.js",
|
|
21
|
+
"test": "jest --watchman=false",
|
|
22
|
+
"test:watch": "jest --watch --watchman=false",
|
|
23
|
+
"test:coverage": "jest --coverage --watchman=false",
|
|
24
|
+
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
|
25
|
+
"lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
|
|
26
|
+
"format": "prettier --write .",
|
|
27
|
+
"format:check": "prettier --check .",
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"check": "npm run lint && npm run typecheck && npm run test",
|
|
30
|
+
"example": "cd example && npm start"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"react-native",
|
|
34
|
+
"birthday",
|
|
35
|
+
"picker",
|
|
36
|
+
"month",
|
|
37
|
+
"day",
|
|
38
|
+
"date",
|
|
39
|
+
"wheel-picker",
|
|
40
|
+
"expo"
|
|
41
|
+
],
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/mpadel78/react-native-month-day-picker.git"
|
|
45
|
+
},
|
|
46
|
+
"author": "mpadel78",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/mpadel78/react-native-month-day-picker/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/mpadel78/react-native-month-day-picker#readme",
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"registry": "https://registry.npmjs.org/"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@quidone/react-native-wheel-picker": "^1.3.5"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@babel/core": "^7.24.0",
|
|
60
|
+
"@babel/preset-env": "^7.24.0",
|
|
61
|
+
"@babel/preset-react": "^7.24.0",
|
|
62
|
+
"@babel/preset-typescript": "^7.24.0",
|
|
63
|
+
"@testing-library/react-native": "^12.4.0",
|
|
64
|
+
"@types/jest": "^29.5.12",
|
|
65
|
+
"@types/react": "^18.2.0",
|
|
66
|
+
"@types/react-native": "^0.72.8",
|
|
67
|
+
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
68
|
+
"@typescript-eslint/parser": "^7.0.0",
|
|
69
|
+
"del-cli": "^5.1.0",
|
|
70
|
+
"eslint": "^8.57.0",
|
|
71
|
+
"eslint-config-prettier": "^10.1.8",
|
|
72
|
+
"eslint-plugin-react": "^7.34.0",
|
|
73
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
74
|
+
"husky": "^9.1.7",
|
|
75
|
+
"jest": "^29.7.0",
|
|
76
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
77
|
+
"lint-staged": "^17.0.5",
|
|
78
|
+
"prettier": "^3.8.3",
|
|
79
|
+
"react": "18.2.0",
|
|
80
|
+
"react-native": "0.74.0",
|
|
81
|
+
"react-native-builder-bob": "^0.23.0",
|
|
82
|
+
"react-test-renderer": "18.2.0",
|
|
83
|
+
"typescript": "^5.4.0"
|
|
84
|
+
},
|
|
85
|
+
"peerDependencies": {
|
|
86
|
+
"react": ">=17.0.0",
|
|
87
|
+
"react-native": ">=0.64.0",
|
|
88
|
+
"react-native-gesture-handler": ">=2.0.0",
|
|
89
|
+
"react-native-reanimated": ">=3.0.0"
|
|
90
|
+
},
|
|
91
|
+
"peerDependenciesMeta": {
|
|
92
|
+
"react-native-gesture-handler": {
|
|
93
|
+
"optional": false
|
|
94
|
+
},
|
|
95
|
+
"react-native-reanimated": {
|
|
96
|
+
"optional": false
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"react-native-builder-bob": {
|
|
100
|
+
"source": "src",
|
|
101
|
+
"output": "lib",
|
|
102
|
+
"targets": [
|
|
103
|
+
"commonjs",
|
|
104
|
+
"module",
|
|
105
|
+
[
|
|
106
|
+
"typescript",
|
|
107
|
+
{
|
|
108
|
+
"project": "tsconfig.build.json"
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
"eslintConfig": {
|
|
114
|
+
"root": true,
|
|
115
|
+
"parser": "@typescript-eslint/parser",
|
|
116
|
+
"plugins": [
|
|
117
|
+
"@typescript-eslint",
|
|
118
|
+
"react",
|
|
119
|
+
"react-hooks"
|
|
120
|
+
],
|
|
121
|
+
"extends": [
|
|
122
|
+
"eslint:recommended",
|
|
123
|
+
"plugin:@typescript-eslint/recommended",
|
|
124
|
+
"plugin:react/recommended",
|
|
125
|
+
"plugin:react-hooks/recommended",
|
|
126
|
+
"prettier"
|
|
127
|
+
],
|
|
128
|
+
"settings": {
|
|
129
|
+
"react": {
|
|
130
|
+
"version": "detect"
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
"rules": {
|
|
134
|
+
"react/react-in-jsx-scope": "off"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { View, StyleSheet } from 'react-native';
|
|
3
|
+
import WheelPicker from '@quidone/react-native-wheel-picker';
|
|
4
|
+
import type { BirthdayPickerProps, BirthdayValue } from './types';
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_BIRTHDAY_VALUE,
|
|
7
|
+
DEFAULT_LOCALE,
|
|
8
|
+
DEFAULT_MONTH_FORMAT,
|
|
9
|
+
DEFAULT_ITEM_HEIGHT,
|
|
10
|
+
DEFAULT_VISIBLE_ITEMS,
|
|
11
|
+
} from './constants';
|
|
12
|
+
import {
|
|
13
|
+
getDaysArray,
|
|
14
|
+
getMonthsArray,
|
|
15
|
+
clampDay,
|
|
16
|
+
getDaysInMonth,
|
|
17
|
+
} from './utils/dateUtils';
|
|
18
|
+
import { getMonthNames, formatDay } from './utils/localeUtils';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Item type for wheel picker
|
|
22
|
+
*/
|
|
23
|
+
interface WheelItem {
|
|
24
|
+
value: number;
|
|
25
|
+
label: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A birthday picker component that allows selecting month and day.
|
|
30
|
+
* Does not include year for privacy-friendly birthday selection.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* // Uncontrolled mode
|
|
35
|
+
* <BirthdayPicker
|
|
36
|
+
* defaultValue={{ month: 6, day: 15 }}
|
|
37
|
+
* onChange={(value) => console.log(value)}
|
|
38
|
+
* />
|
|
39
|
+
*
|
|
40
|
+
* // Controlled mode
|
|
41
|
+
* const [birthday, setBirthday] = useState({ month: 1, day: 1 });
|
|
42
|
+
* <BirthdayPicker
|
|
43
|
+
* value={birthday}
|
|
44
|
+
* onChange={setBirthday}
|
|
45
|
+
* />
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function BirthdayPicker({
|
|
49
|
+
value: controlledValue,
|
|
50
|
+
defaultValue = DEFAULT_BIRTHDAY_VALUE,
|
|
51
|
+
onChange,
|
|
52
|
+
locale = DEFAULT_LOCALE,
|
|
53
|
+
monthFormat = DEFAULT_MONTH_FORMAT,
|
|
54
|
+
allowLeapDay = true,
|
|
55
|
+
disabled = false,
|
|
56
|
+
testID,
|
|
57
|
+
style,
|
|
58
|
+
itemHeight = DEFAULT_ITEM_HEIGHT,
|
|
59
|
+
visibleItems = DEFAULT_VISIBLE_ITEMS,
|
|
60
|
+
monthAccessibilityLabel = 'Month picker',
|
|
61
|
+
dayAccessibilityLabel = 'Day picker',
|
|
62
|
+
}: BirthdayPickerProps): React.ReactElement {
|
|
63
|
+
// Determine if we're in controlled or uncontrolled mode
|
|
64
|
+
const isControlled = controlledValue !== undefined;
|
|
65
|
+
|
|
66
|
+
// Internal state for uncontrolled mode
|
|
67
|
+
const [internalValue, setInternalValue] = React.useState<BirthdayValue>(
|
|
68
|
+
() => controlledValue ?? defaultValue
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// The actual value to display
|
|
72
|
+
const value = isControlled ? controlledValue : internalValue;
|
|
73
|
+
|
|
74
|
+
// Track previous month to detect changes for day clamping
|
|
75
|
+
const prevMonthRef = useRef(value.month);
|
|
76
|
+
|
|
77
|
+
// Get month names based on locale and format
|
|
78
|
+
const monthNames = useMemo(
|
|
79
|
+
() => getMonthNames(locale, monthFormat),
|
|
80
|
+
[locale, monthFormat]
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Generate month items for wheel picker
|
|
84
|
+
const monthItems: WheelItem[] = useMemo(() => {
|
|
85
|
+
return getMonthsArray().map((month) => ({
|
|
86
|
+
value: month,
|
|
87
|
+
label: monthNames[month],
|
|
88
|
+
}));
|
|
89
|
+
}, [monthNames]);
|
|
90
|
+
|
|
91
|
+
// Generate day items based on selected month
|
|
92
|
+
const dayItems: WheelItem[] = useMemo(() => {
|
|
93
|
+
return getDaysArray(value.month, allowLeapDay).map((day) => ({
|
|
94
|
+
value: day,
|
|
95
|
+
label: formatDay(day, locale),
|
|
96
|
+
}));
|
|
97
|
+
}, [value.month, allowLeapDay, locale]);
|
|
98
|
+
|
|
99
|
+
// Handle internal value changes
|
|
100
|
+
const handleValueChange = useCallback(
|
|
101
|
+
(newValue: BirthdayValue) => {
|
|
102
|
+
if (!isControlled) {
|
|
103
|
+
setInternalValue(newValue);
|
|
104
|
+
}
|
|
105
|
+
onChange?.(newValue);
|
|
106
|
+
},
|
|
107
|
+
[isControlled, onChange]
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Handle month change
|
|
111
|
+
const handleMonthChange = useCallback(
|
|
112
|
+
({ item }: { item: WheelItem }) => {
|
|
113
|
+
const newMonth = item.value;
|
|
114
|
+
const clampedDay = clampDay(value.day, newMonth, allowLeapDay);
|
|
115
|
+
|
|
116
|
+
const newValue: BirthdayValue = {
|
|
117
|
+
month: newMonth,
|
|
118
|
+
day: clampedDay,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
handleValueChange(newValue);
|
|
122
|
+
},
|
|
123
|
+
[value.day, allowLeapDay, handleValueChange]
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Handle day change
|
|
127
|
+
const handleDayChange = useCallback(
|
|
128
|
+
({ item }: { item: WheelItem }) => {
|
|
129
|
+
const newValue: BirthdayValue = {
|
|
130
|
+
month: value.month,
|
|
131
|
+
day: item.value,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
handleValueChange(newValue);
|
|
135
|
+
},
|
|
136
|
+
[value.month, handleValueChange]
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// Sync internal state when controlled value changes
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (isControlled && controlledValue) {
|
|
142
|
+
setInternalValue(controlledValue);
|
|
143
|
+
}
|
|
144
|
+
}, [isControlled, controlledValue]);
|
|
145
|
+
|
|
146
|
+
// Clamp day when month changes and day exceeds max days
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
if (value.month !== prevMonthRef.current) {
|
|
149
|
+
const maxDays = getDaysInMonth(value.month, allowLeapDay);
|
|
150
|
+
if (value.day > maxDays) {
|
|
151
|
+
const clampedValue: BirthdayValue = {
|
|
152
|
+
month: value.month,
|
|
153
|
+
day: maxDays,
|
|
154
|
+
};
|
|
155
|
+
handleValueChange(clampedValue);
|
|
156
|
+
}
|
|
157
|
+
prevMonthRef.current = value.month;
|
|
158
|
+
}
|
|
159
|
+
}, [value.month, value.day, allowLeapDay, handleValueChange]);
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<View
|
|
163
|
+
style={[styles.container, style]}
|
|
164
|
+
testID={testID}
|
|
165
|
+
pointerEvents={disabled ? 'none' : 'auto'}
|
|
166
|
+
accessibilityRole="adjustable"
|
|
167
|
+
>
|
|
168
|
+
<View
|
|
169
|
+
style={styles.pickerContainer}
|
|
170
|
+
accessibilityLabel={monthAccessibilityLabel}
|
|
171
|
+
>
|
|
172
|
+
<WheelPicker
|
|
173
|
+
data={monthItems}
|
|
174
|
+
value={value.month}
|
|
175
|
+
onValueChanged={handleMonthChange}
|
|
176
|
+
itemHeight={itemHeight}
|
|
177
|
+
visibleItemCount={visibleItems}
|
|
178
|
+
testID={testID ? `${testID}-month` : undefined}
|
|
179
|
+
/>
|
|
180
|
+
</View>
|
|
181
|
+
|
|
182
|
+
<View
|
|
183
|
+
style={styles.pickerContainer}
|
|
184
|
+
accessibilityLabel={dayAccessibilityLabel}
|
|
185
|
+
>
|
|
186
|
+
<WheelPicker
|
|
187
|
+
data={dayItems}
|
|
188
|
+
value={value.day}
|
|
189
|
+
onValueChanged={handleDayChange}
|
|
190
|
+
itemHeight={itemHeight}
|
|
191
|
+
visibleItemCount={visibleItems}
|
|
192
|
+
testID={testID ? `${testID}-day` : undefined}
|
|
193
|
+
/>
|
|
194
|
+
</View>
|
|
195
|
+
</View>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const styles = StyleSheet.create({
|
|
200
|
+
container: {
|
|
201
|
+
flexDirection: 'row',
|
|
202
|
+
alignItems: 'center',
|
|
203
|
+
justifyContent: 'center',
|
|
204
|
+
},
|
|
205
|
+
pickerContainer: {
|
|
206
|
+
flex: 1,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
export default BirthdayPicker;
|