@elik56a/react-native-pickers 0.0.0-stage → 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.
Files changed (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +276 -2
  3. package/lib/date/Calendar.d.ts +89 -0
  4. package/lib/date/Calendar.d.ts.map +1 -0
  5. package/lib/date/Calendar.js +249 -0
  6. package/lib/date/Calendar.js.map +1 -0
  7. package/lib/date/DatePicker.d.ts +14 -0
  8. package/lib/date/DatePicker.d.ts.map +1 -0
  9. package/lib/date/DatePicker.js +17 -0
  10. package/lib/date/DatePicker.js.map +1 -0
  11. package/lib/date/DateWheelPicker.d.ts +29 -0
  12. package/lib/date/DateWheelPicker.d.ts.map +1 -0
  13. package/lib/date/DateWheelPicker.js +83 -0
  14. package/lib/date/DateWheelPicker.js.map +1 -0
  15. package/lib/emoji/EmojiPicker.d.ts +47 -0
  16. package/lib/emoji/EmojiPicker.d.ts.map +1 -0
  17. package/lib/emoji/EmojiPicker.js +161 -0
  18. package/lib/emoji/EmojiPicker.js.map +1 -0
  19. package/lib/emoji/data.d.ts +2 -0
  20. package/lib/emoji/data.d.ts.map +1 -0
  21. package/lib/emoji/data.js +4 -0
  22. package/lib/emoji/data.js.map +1 -0
  23. package/lib/emoji/emojis.d.ts +23 -0
  24. package/lib/emoji/emojis.d.ts.map +1 -0
  25. package/lib/emoji/emojis.js +100 -0
  26. package/lib/emoji/emojis.js.map +1 -0
  27. package/lib/index.d.ts +17 -0
  28. package/lib/index.d.ts.map +1 -0
  29. package/lib/index.js +17 -0
  30. package/lib/index.js.map +1 -0
  31. package/lib/select/SelectList.d.ts +58 -0
  32. package/lib/select/SelectList.d.ts.map +1 -0
  33. package/lib/select/SelectList.js +91 -0
  34. package/lib/select/SelectList.js.map +1 -0
  35. package/lib/select/SelectPicker.d.ts +35 -0
  36. package/lib/select/SelectPicker.d.ts.map +1 -0
  37. package/lib/select/SelectPicker.js +64 -0
  38. package/lib/select/SelectPicker.js.map +1 -0
  39. package/lib/select/filter.d.ts +3 -0
  40. package/lib/select/filter.d.ts.map +1 -0
  41. package/lib/select/filter.js +13 -0
  42. package/lib/select/filter.js.map +1 -0
  43. package/lib/sheet/BottomSheet.d.ts +29 -0
  44. package/lib/sheet/BottomSheet.d.ts.map +1 -0
  45. package/lib/sheet/BottomSheet.js +89 -0
  46. package/lib/sheet/BottomSheet.js.map +1 -0
  47. package/lib/sheet/PickerSheet.d.ts +26 -0
  48. package/lib/sheet/PickerSheet.d.ts.map +1 -0
  49. package/lib/sheet/PickerSheet.js +27 -0
  50. package/lib/sheet/PickerSheet.js.map +1 -0
  51. package/lib/sheet/sheets.d.ts +48 -0
  52. package/lib/sheet/sheets.d.ts.map +1 -0
  53. package/lib/sheet/sheets.js +58 -0
  54. package/lib/sheet/sheets.js.map +1 -0
  55. package/lib/theme.d.ts +81 -0
  56. package/lib/theme.d.ts.map +1 -0
  57. package/lib/theme.js +96 -0
  58. package/lib/theme.js.map +1 -0
  59. package/lib/time/TimePicker.d.ts +41 -0
  60. package/lib/time/TimePicker.d.ts.map +1 -0
  61. package/lib/time/TimePicker.js +105 -0
  62. package/lib/time/TimePicker.js.map +1 -0
  63. package/lib/types.d.ts +9 -0
  64. package/lib/types.d.ts.map +1 -0
  65. package/lib/types.js +2 -0
  66. package/lib/types.js.map +1 -0
  67. package/lib/utils/OwnScroll.d.ts +10 -0
  68. package/lib/utils/OwnScroll.d.ts.map +1 -0
  69. package/lib/utils/OwnScroll.js +13 -0
  70. package/lib/utils/OwnScroll.js.map +1 -0
  71. package/lib/utils/date.d.ts +30 -0
  72. package/lib/utils/date.d.ts.map +1 -0
  73. package/lib/utils/date.js +115 -0
  74. package/lib/utils/date.js.map +1 -0
  75. package/lib/wheel/MultiWheelPicker.d.ts +26 -0
  76. package/lib/wheel/MultiWheelPicker.d.ts.map +1 -0
  77. package/lib/wheel/MultiWheelPicker.js +28 -0
  78. package/lib/wheel/MultiWheelPicker.js.map +1 -0
  79. package/lib/wheel/WheelPicker.d.ts +59 -0
  80. package/lib/wheel/WheelPicker.d.ts.map +1 -0
  81. package/lib/wheel/WheelPicker.js +257 -0
  82. package/lib/wheel/WheelPicker.js.map +1 -0
  83. package/lib/wheel/typeToSelect.d.ts +8 -0
  84. package/lib/wheel/typeToSelect.d.ts.map +1 -0
  85. package/lib/wheel/typeToSelect.js +10 -0
  86. package/lib/wheel/typeToSelect.js.map +1 -0
  87. package/package.json +68 -3
  88. package/src/date/Calendar.tsx +586 -0
  89. package/src/date/DatePicker.tsx +22 -0
  90. package/src/date/DateWheelPicker.tsx +149 -0
  91. package/src/emoji/EmojiPicker.tsx +410 -0
  92. package/src/emoji/data.ts +3 -0
  93. package/src/emoji/emojis.ts +127 -0
  94. package/src/index.ts +16 -0
  95. package/src/select/SelectList.tsx +263 -0
  96. package/src/select/SelectPicker.tsx +203 -0
  97. package/src/select/filter.ts +17 -0
  98. package/src/sheet/BottomSheet.tsx +199 -0
  99. package/src/sheet/PickerSheet.tsx +86 -0
  100. package/src/sheet/sheets.tsx +218 -0
  101. package/src/theme.tsx +160 -0
  102. package/src/time/TimePicker.tsx +199 -0
  103. package/src/types.ts +10 -0
  104. package/src/utils/OwnScroll.tsx +14 -0
  105. package/src/utils/date.ts +131 -0
  106. package/src/wheel/MultiWheelPicker.tsx +96 -0
  107. package/src/wheel/WheelPicker.tsx +534 -0
  108. package/src/wheel/typeToSelect.ts +12 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eliyahu Kriel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,277 @@
1
- # Temporary Holding Version
1
+ # react-native-pickers
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Beautiful, fully customizable pickers for React Native — **100% JavaScript, zero native code.**
4
+ Works with Expo Go, bare React Native, and React Native Web out of the box.
5
+
6
+ | | |
7
+ |---|---|
8
+ | 🎡 **WheelPicker** | iOS-style 3D wheel, infinite loop, disabled items, multi-column |
9
+ | ⏰ **TimePicker** | 12h / 24h (auto from locale), minute & second intervals, min/max time |
10
+ | 📅 **DatePicker** | Calendar (single · range · multiple) or wheel, min/max, disabled days, marked dates, i18n |
11
+ | 📋 **SelectPicker** | Field + bottom sheet, search, groups, multi-select with chips, icons |
12
+ | 😀 **EmojiPicker** | 1,900+ emojis, categories, search, skin tones, recents, version filtering |
13
+
14
+ Every picker also ships as a **bottom sheet** (`DatePickerSheet`, `TimePickerSheet`, `WheelPickerSheet`, `EmojiPickerSheet`), with drag-to-dismiss and Cancel / Done.
15
+
16
+ ```sh
17
+ npm install @elik56a/react-native-pickers
18
+ ```
19
+
20
+ No other dependencies. Requires `react >= 18` and `react-native >= 0.72`.
21
+
22
+ ---
23
+
24
+ ## Quick start
25
+
26
+ ```tsx
27
+ import { DatePicker, TimePicker, SelectPicker, EmojiPicker, WheelPicker } from '@elik56a/react-native-pickers';
28
+
29
+ <WheelPicker items={['S', 'M', 'L', 'XL']} value={size} onChange={setSize} />
30
+
31
+ <TimePicker value={time} onChange={setTime} minuteInterval={5} />
32
+
33
+ <DatePicker value={date} onChange={setDate} minDate={new Date()} />
34
+ <DatePicker mode="range" value={range} onChange={setRange} />
35
+ <DatePicker display="wheel" value={birthday} onChange={setBirthday} />
36
+
37
+ <SelectPicker label="Country" options={countries} value={country} onChange={setCountry} />
38
+
39
+ <EmojiPicker onSelect={(emoji) => setText((t) => t + emoji)} />
40
+ ```
41
+
42
+ Bottom sheets keep a draft and only commit on **Done**:
43
+
44
+ ```tsx
45
+ <DatePickerSheet
46
+ visible={open}
47
+ onClose={() => setOpen(false)}
48
+ title="Check-in / Check-out"
49
+ mode="range"
50
+ value={range}
51
+ onConfirm={setRange}
52
+ />
53
+ ```
54
+
55
+ Every picker works **controlled** (`value` + `onChange`) or **uncontrolled** (`defaultValue`).
56
+
57
+ ---
58
+
59
+ ## Theming
60
+
61
+ Wrap your app (optional — sensible light & dark defaults apply without it):
62
+
63
+ ```tsx
64
+ import { PickersProvider } from '@elik56a/react-native-pickers';
65
+
66
+ <PickersProvider
67
+ colorScheme="auto" // 'auto' | 'light' | 'dark'
68
+ theme={{ colors: { primary: '#FF5A5F' }, radius: { md: 8 }, typography: { fontFamily: 'Inter' } }}
69
+ darkTheme={{ colors: { primary: '#FF8A8E', background: '#000' } }}
70
+ >
71
+ <App />
72
+ </PickersProvider>
73
+ ```
74
+
75
+ Any component also accepts `theme` for a local override. Available tokens:
76
+
77
+ - `colors`: background, surface, sheet, text, textSecondary, textDisabled, primary, onPrimary, primaryMuted, border, backdrop, indicator, handle
78
+ - `radius`: sm, md, lg, full, sheet
79
+ - `spacing`: xs, sm, md, lg, xl
80
+ - `typography`: fontFamily, fontFamilyMedium, sizes, weights
81
+ - `animation`: duration
82
+
83
+ ### Slot styles
84
+
85
+ Every visual part is reachable through `styles`, so you never have to fork:
86
+
87
+ ```tsx
88
+ <DatePicker
89
+ styles={{
90
+ selectedDay: { borderRadius: 8 },
91
+ dayText: { fontFamily: 'Inter' },
92
+ rangeBand: { backgroundColor: '#FFE4E6' },
93
+ headerTitle: { fontSize: 20 },
94
+ }}
95
+ />
96
+ ```
97
+
98
+ ### Render props
99
+
100
+ For full control: `renderItem` / `renderIndicator` (wheel), `renderDay` / `renderArrow` (calendar), `renderOption` / `renderTrigger` / `renderGroupHeader` (select), `renderEmoji` (emoji).
101
+
102
+ ---
103
+
104
+ ## API
105
+
106
+ ### WheelPicker
107
+
108
+ | Prop | Type | Default | |
109
+ |---|---|---|---|
110
+ | `items` | `(T \| { label, value, disabled? })[]` | — | Primitives or objects |
111
+ | `value` / `defaultValue` | `T` | — | |
112
+ | `onChange` | `(value, index) => void` | — | Fires when the wheel settles |
113
+ | `onValueChanging` | `(value, index) => void` | — | Fires on every tick — hook haptics here |
114
+ | `itemHeight` | `number` | `40` | |
115
+ | `visibleItems` | `number` (odd) | `5` | |
116
+ | `loop` | `boolean` | `false` | Infinite wheel |
117
+ | `keyboardInput` | `boolean` | `true` | Tap the selected row to type a value; the wheel jumps to the first matching label |
118
+ | `variant` | `'3d' \| 'flat'` | `'3d'` | |
119
+ | `rotation` | `number` | `20` | Degrees between rows (3d) |
120
+ | `fadeTo` | `number` | `0.3` | Opacity of the farthest rows |
121
+ | `isEqual` | `(a, b) => boolean` | `Object.is` | For object values |
122
+ | `styles` | slots: `container item itemText selectedItemText disabledItemText indicator` | | |
123
+
124
+ `MultiWheelPicker` renders several wheels sharing one selection band: `columns={[{ key, items, value, onChange, flex, loop }]}` and an optional `separator`.
125
+
126
+ Haptics example (no dependency imposed — bring your own):
127
+
128
+ ```tsx
129
+ import * as Haptics from 'expo-haptics';
130
+ <WheelPicker onValueChanging={() => Haptics.selectionAsync()} ... />
131
+ ```
132
+
133
+ ### TimePicker
134
+
135
+ | Prop | Type | Default |
136
+ |---|---|---|
137
+ | `value` / `defaultValue` | `Date` | now |
138
+ | `onChange` / `onValueChanging` | `(date) => void` | |
139
+ | `format` | `'12h' \| '24h' \| 'auto'` | `'auto'` (from `locale`) |
140
+ | `locale` | `string` | device |
141
+ | `minuteInterval` | `number` | `1` |
142
+ | `showSeconds` / `secondInterval` | `boolean` / `number` | `false` / `1` |
143
+ | `minTime` / `maxTime` | `Date` (time of day) | |
144
+ | `loop` | `boolean` | `true` |
145
+ | `dayPeriodLabels` | `[am, pm]` | from locale |
146
+ | `dayPeriodFirst` | `boolean` | `false` |
147
+
148
+ ### DatePicker
149
+
150
+ `display="calendar"` (default) or `display="wheel"`.
151
+
152
+ Selection modes:
153
+
154
+ ```ts
155
+ mode?: 'single' value: Date | null onChange(date)
156
+ mode: 'range' value: { start, end } onChange(range) minRangeDays, maxRangeDays, allowSingleDayRange
157
+ mode: 'multiple' value: Date[] onChange(dates) maxSelections
158
+ ```
159
+
160
+ | Prop | Type | Default | |
161
+ |---|---|---|---|
162
+ | `minDate` / `maxDate` | `Date` | | |
163
+ | `isDateDisabled` | `(date) => boolean` | | e.g. weekends |
164
+ | `markedDates` | `{ 'YYYY-MM-DD': { dots?, label?, disabled?, textColor? } }` | | Dots, prices, events |
165
+ | `locale` | `string` | device | Month & weekday names via `Intl` |
166
+ | `firstDayOfWeek` | `0–6` | from locale | |
167
+ | `showOutsideDays` / `fixedWeeks` | `boolean` | `true` | |
168
+ | `enableSwipe` | `boolean` | `true` | Swipe between months |
169
+ | `enableQuickJump` | `boolean` | `true` | Tap title → month grid → year grid |
170
+ | `weekdayFormat` | `'short' \| 'narrow'` | `'short'` | |
171
+ | `dayHeight` | `number` | `44` | |
172
+ | `onMonthChange` | `(month) => void` | | |
173
+
174
+ Ranges never cross disabled days — tapping past one starts a new range.
175
+
176
+ Wheel mode: `order` (`['day','month','year']`, defaults from locale), `monthFormat` (`'long' | 'short' | 'numeric'`), `yearRange`.
177
+
178
+ ### SelectPicker / SelectList
179
+
180
+ `SelectPicker` is a field that opens a sheet; `SelectList` is the inline list.
181
+
182
+ ```ts
183
+ options: { label, value, description?, icon?, group?, disabled?, keywords? }[]
184
+ multiple?: false value: T | null onChange(value, option)
185
+ multiple: true value: T[] onChange(values, options) maxSelected
186
+ ```
187
+
188
+ | Prop | Default | |
189
+ |---|---|---|
190
+ | `searchable` | `> 8 options` | Accent- and niqqud-insensitive |
191
+ | `filterOption` | | Custom search |
192
+ | `placeholder` / `label` / `sheetTitle` | | |
193
+ | `clearable` | `false` | |
194
+ | `chips` | `true` | Removable chips in multi mode |
195
+ | `closeOnSelect` | `true` | Single mode |
196
+ | `disabled` | `false` | |
197
+
198
+ ### EmojiPicker
199
+
200
+ | Prop | Default | |
201
+ |---|---|---|
202
+ | `onSelect` | — | `(emoji: string, data: Emoji) => void` |
203
+ | `columns` / `emojiSize` / `height` | `8` / auto / `380` | |
204
+ | `categories` | all | Filter & reorder |
205
+ | `exclude` | | Characters to hide |
206
+ | `maxEmojiVersion` | | Hide emojis older devices can't render |
207
+ | `skinTone` / `defaultSkinTone` / `onSkinToneChange` | `0` | 0 = default, 1–5 light → dark |
208
+ | `recent` / `defaultRecent` / `onRecentChange` / `maxRecent` | `[]` / `24` | Persist with your own storage |
209
+ | `categoryBarPosition` | `'top'` | or `'bottom'` |
210
+ | `categoryLabels` / `categoryIcons` | | i18n and custom icons |
211
+ | `searchPlaceholder` / `emptyText` | | |
212
+
213
+ Persisting recents with AsyncStorage:
214
+
215
+ ```tsx
216
+ const [recent, setRecent] = useState<string[]>([]);
217
+ useEffect(() => { AsyncStorage.getItem('recent').then((v) => v && setRecent(JSON.parse(v))); }, []);
218
+
219
+ <EmojiPicker
220
+ recent={recent}
221
+ onRecentChange={(r) => { setRecent(r); AsyncStorage.setItem('recent', JSON.stringify(r)); }}
222
+ onSelect={...}
223
+ />
224
+ ```
225
+
226
+ Utilities: `getAllEmojis()`, `searchEmojis(query)`, `findEmoji(char)`, `withSkinTone(emoji, tone)`.
227
+
228
+ ### BottomSheet
229
+
230
+ Use the same sheet for your own content:
231
+
232
+ ```tsx
233
+ <BottomSheet visible={open} onClose={close} title="Filters" footer={<ApplyButton />}>
234
+ ...
235
+ </BottomSheet>
236
+ ```
237
+
238
+ Props: `title`, `headerLeft`, `headerRight`, `footer`, `enableSwipeToClose`, `closeOnBackdropPress`, `height`, `maxHeightRatio`, `bottomInset`, `styles`.
239
+
240
+ ---
241
+
242
+ ## RTL & i18n
243
+
244
+ Layouts use logical `start` / `end`, so they flip automatically with `I18nManager.isRTL`; calendar arrows and swipe direction follow. All month, weekday, and AM/PM names come from `Intl` (Hermes supports it), with English fallbacks. Every user-facing string (Done, Cancel, Search, empty states, category labels) is a prop.
245
+
246
+ ## Accessibility
247
+
248
+ Wheels are `adjustable` with increment / decrement actions for VoiceOver and TalkBack. Days announce their full date, and selected / disabled states are exposed everywhere.
249
+
250
+ ## Performance
251
+
252
+ Animations run on the native driver. Wheels and the emoji grid are virtualized `FlatList`s with fixed row layouts, and rows are memoized so scrolling re-renders only the two rows that change.
253
+
254
+ ## Development
255
+
256
+ ```sh
257
+ npm test # unit tests
258
+ npm run typecheck
259
+ npm run build # lib/ (ESM + .d.ts)
260
+ npm run example:web # bundles example/App.tsx for React Native Web → example/web/dist
261
+ npm run gen:emoji # regenerate emoji data from emojibase-data
262
+ ```
263
+
264
+ ### Running the example on iOS / Android
265
+
266
+ [`example/native`](example/native) is an Expo app that renders [`example/App.tsx`](example/App.tsx) against the library source, so edits in `src/` hot-reload.
267
+
268
+ ```sh
269
+ pnpm install # from the repo root (pnpm workspace)
270
+ pnpm example # press i for the iOS Simulator, a for the Android emulator
271
+ ```
272
+
273
+ Emoji data comes from [emojibase](https://emojibase.dev) (MIT).
274
+
275
+ ## License
276
+
277
+ MIT
@@ -0,0 +1,89 @@
1
+ import React from 'react';
2
+ import type { StyleProp, ViewStyle } from 'react-native';
3
+ import type { PickerTheme } from '../theme';
4
+ import type { SlotStyles, ThemableProps } from '../types';
5
+ export interface DateRange {
6
+ start: Date | null;
7
+ end: Date | null;
8
+ }
9
+ export interface MarkedDate {
10
+ /** Dot colors under the day number */
11
+ dots?: string[];
12
+ disabled?: boolean;
13
+ /** Small text under the day (e.g. a price) */
14
+ label?: string;
15
+ textColor?: string;
16
+ }
17
+ export type CalendarSlot = 'container' | 'header' | 'headerTitle' | 'arrow' | 'arrowText' | 'weekdays' | 'weekdayText' | 'week' | 'day' | 'dayInner' | 'dayText' | 'selectedDay' | 'selectedDayText' | 'todayDay' | 'todayDayText' | 'disabledDayText' | 'outsideDayText' | 'rangeBand' | 'dot' | 'dayLabel' | 'gridCell' | 'gridCellText';
18
+ export interface DayState {
19
+ date: Date;
20
+ selected: boolean;
21
+ rangeStart: boolean;
22
+ rangeEnd: boolean;
23
+ inRange: boolean;
24
+ today: boolean;
25
+ disabled: boolean;
26
+ outside: boolean;
27
+ marking?: MarkedDate;
28
+ }
29
+ export interface CalendarBaseProps extends ThemableProps {
30
+ locale?: string;
31
+ minDate?: Date;
32
+ maxDate?: Date;
33
+ /** Return true to disable a day */
34
+ isDateDisabled?: (date: Date) => boolean;
35
+ /** 0 = Sunday ... 6 = Saturday. Default: from locale, else 0 */
36
+ firstDayOfWeek?: number;
37
+ /** Keyed by `YYYY-MM-DD` */
38
+ markedDates?: Record<string, MarkedDate>;
39
+ /** Month shown initially. Default: the selected date or today */
40
+ initialMonth?: Date;
41
+ onMonthChange?: (month: Date) => void;
42
+ showOutsideDays?: boolean;
43
+ /** Always render 6 weeks to keep the height stable. Default: true */
44
+ fixedWeeks?: boolean;
45
+ /** Swipe left/right to change months. Default: true */
46
+ enableSwipe?: boolean;
47
+ /** Tap the title to jump by month / year. Default: true */
48
+ enableQuickJump?: boolean;
49
+ weekdayFormat?: 'short' | 'narrow';
50
+ showWeekdays?: boolean;
51
+ /** Override the title text */
52
+ formatTitle?: (month: Date, locale?: string) => string;
53
+ /** Fully custom day cell */
54
+ renderDay?: (state: DayState, theme: PickerTheme) => React.ReactNode;
55
+ /** Custom arrows */
56
+ renderArrow?: (direction: 'prev' | 'next', disabled: boolean) => React.ReactNode;
57
+ /** Range of years in the year grid. Default: minDate/maxDate or ±100 years */
58
+ yearRange?: [number, number];
59
+ /** Day cell height. Default: 44 */
60
+ dayHeight?: number;
61
+ style?: StyleProp<ViewStyle>;
62
+ styles?: SlotStyles<CalendarSlot>;
63
+ testID?: string;
64
+ }
65
+ export type CalendarSelectionProps = {
66
+ mode?: 'single';
67
+ value?: Date | null;
68
+ defaultValue?: Date | null;
69
+ onChange?: (date: Date) => void;
70
+ } | {
71
+ mode: 'range';
72
+ value?: DateRange;
73
+ defaultValue?: DateRange;
74
+ onChange?: (range: DateRange) => void;
75
+ /** Minimum nights in a range (end - start, in days) */
76
+ minRangeDays?: number;
77
+ maxRangeDays?: number;
78
+ /** Allow start === end. Default: true */
79
+ allowSingleDayRange?: boolean;
80
+ } | {
81
+ mode: 'multiple';
82
+ value?: Date[];
83
+ defaultValue?: Date[];
84
+ onChange?: (dates: Date[]) => void;
85
+ maxSelections?: number;
86
+ };
87
+ export type CalendarProps = CalendarBaseProps & CalendarSelectionProps;
88
+ export declare function Calendar(props: CalendarProps): React.JSX.Element;
89
+ //# sourceMappingURL=Calendar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Calendar.d.ts","sourceRoot":"","sources":["../../src/date/Calendar.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,249 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+ import { Animated, I18nManager, PanResponder, Platform, Pressable, StyleSheet, Text, View } from 'react-native';
4
+ import { textStyle, usePickerTheme } from '../theme';
5
+ import { addMonths, buildMonthGrid, compareDay, formatDate, formatMonthYear, getFirstDayOfWeek, getMonthNames, getWeekdayNames, isBetweenDays, isOutOfRange, isSameDay, startOfMonth, toDateKey, } from '../utils/date';
6
+ const DAY_MS = 86400000;
7
+ export function Calendar(props) {
8
+ const { locale, minDate, maxDate, isDateDisabled, markedDates, initialMonth, onMonthChange, showOutsideDays = true, fixedWeeks = true, enableSwipe = true, enableQuickJump = true, weekdayFormat = 'short', showWeekdays = true, formatTitle, renderDay, renderArrow, yearRange, dayHeight = 44, style, styles: slots, theme: themeOverride, testID, } = props;
9
+ const theme = usePickerTheme(themeOverride);
10
+ const mode = props.mode ?? 'single';
11
+ const firstDay = props.firstDayOfWeek ?? getFirstDayOfWeek(locale);
12
+ const rtl = !!I18nManager.isRTL;
13
+ // ----- selection state (controlled or uncontrolled) -----
14
+ const [innerSingle, setInnerSingle] = useState(mode === 'single' ? (props.defaultValue ?? null) : null);
15
+ const [innerRange, setInnerRange] = useState(mode === 'range' ? (props.defaultValue ?? { start: null, end: null }) : { start: null, end: null });
16
+ const [innerMulti, setInnerMulti] = useState(mode === 'multiple' ? (props.defaultValue ?? []) : []);
17
+ const single = mode === 'single' ? (props.value !== undefined ? props.value : innerSingle) : null;
18
+ const range = mode === 'range' ? (props.value ?? innerRange) : { start: null, end: null };
19
+ const multi = mode === 'multiple' ? (props.value ?? innerMulti) : [];
20
+ const anchor = initialMonth ?? single ?? range.start ?? multi[0] ?? new Date();
21
+ const [month, setMonth] = useState(() => startOfMonth(anchor));
22
+ const [view, setView] = useState('days');
23
+ const today = useMemo(() => new Date(), []);
24
+ // Follow external value changes in single mode
25
+ const singleKey = single ? toDateKey(single) : '';
26
+ useEffect(() => {
27
+ if (single && view === 'days' && (single.getMonth() !== month.getMonth() || single.getFullYear() !== month.getFullYear())) {
28
+ setMonth(startOfMonth(single));
29
+ }
30
+ // eslint-disable-next-line react-hooks/exhaustive-deps
31
+ }, [singleKey]);
32
+ const isDisabled = useCallback((d) => isOutOfRange(d, minDate, maxDate) || !!isDateDisabled?.(d) || !!markedDates?.[toDateKey(d)]?.disabled, [isDateDisabled, markedDates, maxDate, minDate]);
33
+ // ----- month navigation with slide animation -----
34
+ const slide = useRef(new Animated.Value(0)).current;
35
+ const native = Platform.OS !== 'web';
36
+ const canPrev = !minDate || compareDay(addMonths(month, -1), startOfMonth(minDate)) >= 0;
37
+ const canNext = !maxDate || compareDay(addMonths(month, 1), startOfMonth(maxDate)) <= 0;
38
+ const goMonth = useCallback((delta) => {
39
+ if ((delta < 0 && !canPrev) || (delta > 0 && !canNext))
40
+ return;
41
+ const next = addMonths(month, delta);
42
+ setMonth(next);
43
+ onMonthChange?.(next);
44
+ slide.setValue(delta * (rtl ? -1 : 1));
45
+ Animated.timing(slide, { toValue: 0, duration: theme.animation.duration, useNativeDriver: native }).start();
46
+ }, [canNext, canPrev, month, native, onMonthChange, rtl, slide, theme.animation.duration]);
47
+ const goRef = useRef(goMonth);
48
+ goRef.current = goMonth;
49
+ const pan = useRef(PanResponder.create({
50
+ onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dx) > 14 && Math.abs(g.dx) > Math.abs(g.dy) * 1.5,
51
+ onPanResponderRelease: (_, g) => {
52
+ if (Math.abs(g.dx) > 50 || Math.abs(g.vx) > 0.5) {
53
+ const dir = g.dx < 0 ? 1 : -1;
54
+ goRef.current(I18nManager.isRTL === true ? -dir : dir);
55
+ }
56
+ },
57
+ })).current;
58
+ // ----- selection logic -----
59
+ const select = useCallback((d) => {
60
+ if (mode === 'single') {
61
+ if (props.value === undefined)
62
+ setInnerSingle(d);
63
+ props.onChange?.(d);
64
+ }
65
+ else if (mode === 'range') {
66
+ const p = props;
67
+ let next;
68
+ if (!range.start || range.end || compareDay(d, range.start) < 0) {
69
+ next = { start: d, end: null };
70
+ }
71
+ else {
72
+ const days = Math.round((d.getTime() - range.start.getTime()) / DAY_MS);
73
+ if (days === 0 && p.allowSingleDayRange === false)
74
+ return;
75
+ if (p.minRangeDays !== undefined && days < p.minRangeDays)
76
+ return;
77
+ if (p.maxRangeDays !== undefined && days > p.maxRangeDays)
78
+ next = { start: d, end: null };
79
+ else {
80
+ // Reject ranges that cross disabled days
81
+ let crosses = false;
82
+ for (let t = new Date(range.start); compareDay(t, d) < 0; t = new Date(t.getFullYear(), t.getMonth(), t.getDate() + 1)) {
83
+ if (isDisabled(t)) {
84
+ crosses = true;
85
+ break;
86
+ }
87
+ }
88
+ next = crosses ? { start: d, end: null } : { start: range.start, end: d };
89
+ }
90
+ }
91
+ if (p.value === undefined)
92
+ setInnerRange(next);
93
+ p.onChange?.(next);
94
+ }
95
+ else {
96
+ const p = props;
97
+ const exists = multi.some((x) => isSameDay(x, d));
98
+ if (!exists && p.maxSelections !== undefined && multi.length >= p.maxSelections)
99
+ return;
100
+ const next = exists ? multi.filter((x) => !isSameDay(x, d)) : [...multi, d].sort((a, b) => a.getTime() - b.getTime());
101
+ if (p.value === undefined)
102
+ setInnerMulti(next);
103
+ p.onChange?.(next);
104
+ }
105
+ }, [isDisabled, mode, multi, props, range.end, range.start]);
106
+ const grid = useMemo(() => buildMonthGrid(month.getFullYear(), month.getMonth(), firstDay, fixedWeeks), [firstDay, fixedWeeks, month]);
107
+ const weekdayNames = useMemo(() => {
108
+ const names = getWeekdayNames(locale, weekdayFormat);
109
+ return Array.from({ length: 7 }, (_, i) => names[(firstDay + i) % 7]);
110
+ }, [firstDay, locale, weekdayFormat]);
111
+ const title = formatTitle ? formatTitle(month, locale) : formatMonthYear(month, locale);
112
+ const accent = theme.colors.primary;
113
+ const header = (_jsxs(View, { style: [s.header, { paddingHorizontal: theme.spacing.sm, marginBottom: theme.spacing.sm }, slots?.header], children: [_jsxs(Pressable, { onPress: enableQuickJump ? () => setView(view === 'days' ? 'months' : 'days') : undefined, style: ({ pressed }) => [s.titleBtn, { borderRadius: theme.radius.sm, opacity: pressed ? 0.6 : 1 }], accessibilityRole: enableQuickJump ? 'button' : 'header', accessibilityLabel: title, children: [_jsx(Text, { style: [textStyle(theme, 'md', 'bold'), slots?.headerTitle], children: view === 'years' ? yearPageLabel(month.getFullYear()) : view === 'months' ? String(month.getFullYear()) : title }), enableQuickJump && (_jsx(Text, { style: [textStyle(theme, 'sm', 'bold'), { color: accent, marginStart: 6 }], children: view === 'days' ? '▾' : '▴' }))] }), _jsx(View, { style: s.arrows, children: ['prev', 'next'].map((dir) => {
114
+ const disabled = view === 'days' ? (dir === 'prev' ? !canPrev : !canNext) : false;
115
+ const onPress = () => {
116
+ const delta = dir === 'prev' ? -1 : 1;
117
+ if (view === 'days')
118
+ goMonth(delta);
119
+ else if (view === 'months')
120
+ setMonth(new Date(month.getFullYear() + delta, month.getMonth(), 1));
121
+ else
122
+ setMonth(new Date(month.getFullYear() + delta * 12, month.getMonth(), 1));
123
+ };
124
+ return (_jsx(Pressable, { onPress: disabled ? undefined : onPress, hitSlop: 8, style: ({ pressed }) => [
125
+ s.arrow,
126
+ { borderRadius: theme.radius.full, backgroundColor: pressed ? theme.colors.surface : 'transparent' },
127
+ slots?.arrow,
128
+ ], accessibilityRole: "button", accessibilityLabel: dir === 'prev' ? 'Previous' : 'Next', accessibilityState: { disabled }, children: renderArrow ? (renderArrow(dir, disabled)) : (_jsx(Text, { style: [
129
+ textStyle(theme, 'lg', 'bold'),
130
+ { color: disabled ? theme.colors.textDisabled : accent, lineHeight: 22 },
131
+ slots?.arrowText,
132
+ ], children: (dir === 'prev') !== rtl ? '‹' : '›' })) }, dir));
133
+ }) })] }));
134
+ const translateX = slide.interpolate({ inputRange: [-1, 0, 1], outputRange: [-40, 0, 40] });
135
+ const opacity = slide.interpolate({ inputRange: [-1, 0, 1], outputRange: [0, 1, 0] });
136
+ let body;
137
+ if (view === 'months') {
138
+ const names = getMonthNames(locale, 'short');
139
+ body = (_jsx(QuickGrid, { theme: theme, slots: slots, cells: names.map((label, m) => {
140
+ const d = new Date(month.getFullYear(), m, 1);
141
+ const disabled = (!!minDate && compareDay(new Date(month.getFullYear(), m + 1, 0), minDate) < 0) || (!!maxDate && compareDay(d, maxDate) > 0);
142
+ return { key: String(m), label, selected: m === month.getMonth(), disabled, onPress: () => {
143
+ setMonth(d);
144
+ onMonthChange?.(d);
145
+ setView('days');
146
+ } };
147
+ }), onTitle: () => setView('years'), height: dayHeight * (fixedWeeks ? 6 : 5) + (showWeekdays ? 28 : 0) }));
148
+ }
149
+ else if (view === 'years') {
150
+ const y0 = Math.floor(month.getFullYear() / 12) * 12;
151
+ const [minY, maxY] = yearRange ?? [minDate?.getFullYear() ?? today.getFullYear() - 100, maxDate?.getFullYear() ?? today.getFullYear() + 100];
152
+ body = (_jsx(QuickGrid, { theme: theme, slots: slots, cells: Array.from({ length: 12 }, (_, i) => {
153
+ const y = y0 + i;
154
+ return { key: String(y), label: String(y), selected: y === month.getFullYear(), disabled: y < minY || y > maxY, onPress: () => {
155
+ setMonth(new Date(y, month.getMonth(), 1));
156
+ setView('months');
157
+ } };
158
+ }), height: dayHeight * (fixedWeeks ? 6 : 5) + (showWeekdays ? 28 : 0) }));
159
+ }
160
+ else {
161
+ body = (_jsxs(Animated.View, { style: { transform: [{ translateX }], opacity }, ...(enableSwipe ? pan.panHandlers : {}), children: [showWeekdays && (_jsx(View, { style: [s.week, { marginBottom: 4 }, slots?.weekdays], children: weekdayNames.map((w, i) => (_jsx(View, { style: s.cell, children: _jsx(Text, { style: [textStyle(theme, 'xs', 'medium'), { color: theme.colors.textSecondary, textAlign: 'center' }, slots?.weekdayText], children: w }) }, i))) })), grid.map((week, wi) => (_jsx(View, { style: [s.week, slots?.week], children: week.map((d) => {
162
+ const outside = d.getMonth() !== month.getMonth();
163
+ const rangeStart = mode === 'range' && isSameDay(d, range.start);
164
+ const rangeEnd = mode === 'range' && isSameDay(d, range.end);
165
+ const state = {
166
+ date: d,
167
+ outside,
168
+ today: isSameDay(d, today),
169
+ disabled: isDisabled(d),
170
+ rangeStart,
171
+ rangeEnd,
172
+ inRange: mode === 'range' && isBetweenDays(d, range.start, range.end),
173
+ selected: mode === 'single' ? isSameDay(d, single) : mode === 'range' ? rangeStart || rangeEnd : multi.some((x) => isSameDay(x, d)),
174
+ marking: markedDates?.[toDateKey(d)],
175
+ };
176
+ return (_jsx(DayCell, { state: state, hidden: outside && !showOutsideDays, hasRangeEnd: !!range.end, theme: theme, slots: slots, height: dayHeight, locale: locale, renderDay: renderDay, onPress: select }, d.getTime()));
177
+ }) }, wi)))] }));
178
+ }
179
+ return (_jsxs(View, { style: [{ backgroundColor: theme.colors.background, paddingVertical: theme.spacing.sm }, style, slots?.container], testID: testID, children: [header, body] }));
180
+ }
181
+ function yearPageLabel(year) {
182
+ const y0 = Math.floor(year / 12) * 12;
183
+ return `${y0} – ${y0 + 11}`;
184
+ }
185
+ const DayCell = memo(function DayCell({ state, hidden, hasRangeEnd, theme, slots, height, locale, renderDay, onPress }) {
186
+ const { date, selected, disabled, outside, today, inRange, rangeStart, rangeEnd, marking } = state;
187
+ if (hidden)
188
+ return _jsx(View, { style: [s.cell, { height }] });
189
+ const c = theme.colors;
190
+ const size = Math.min(height - 4, 40);
191
+ const band = inRange || (hasRangeEnd && (rangeStart || rangeEnd) && !(rangeStart && rangeEnd));
192
+ const textColor = selected
193
+ ? c.onPrimary
194
+ : disabled
195
+ ? c.textDisabled
196
+ : marking?.textColor ?? (outside ? c.textDisabled : today ? c.primary : c.text);
197
+ return (_jsx(Pressable, { onPress: disabled ? undefined : () => onPress(date), style: [s.cell, { height }, slots?.day], accessibilityRole: "button", accessibilityLabel: formatDate(date, locale, { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }), accessibilityState: { selected, disabled }, children: ({ pressed }) => (_jsxs(_Fragment, { children: [band && (_jsx(View, { style: [
198
+ s.band,
199
+ { top: (height - size) / 2, height: size, backgroundColor: c.primaryMuted },
200
+ rangeStart ? { start: '50%' } : rangeEnd ? { end: '50%' } : null,
201
+ slots?.rangeBand,
202
+ ] })), renderDay ? (renderDay(state, theme)) : (_jsxs(View, { style: [
203
+ s.dayInner,
204
+ { width: size, height: size, borderRadius: size / 2 },
205
+ selected && { backgroundColor: c.primary },
206
+ !selected && pressed && !disabled && { backgroundColor: c.surface },
207
+ !selected && today && { borderWidth: 1.5, borderColor: c.primary },
208
+ slots?.dayInner,
209
+ today && slots?.todayDay,
210
+ selected && slots?.selectedDay,
211
+ ], children: [_jsx(Text, { style: [
212
+ textStyle(theme, 'md', selected || today ? 'bold' : 'regular'),
213
+ { color: textColor, textDecorationLine: disabled && !outside ? 'line-through' : 'none' },
214
+ slots?.dayText,
215
+ outside && slots?.outsideDayText,
216
+ disabled && slots?.disabledDayText,
217
+ today && slots?.todayDayText,
218
+ selected && slots?.selectedDayText,
219
+ ], children: date.getDate() }), marking?.label ? (_jsx(Text, { style: [{ fontSize: 9, color: selected ? c.onPrimary : c.textSecondary, marginTop: -2 }, slots?.dayLabel], numberOfLines: 1, children: marking.label })) : null, marking?.dots?.length ? (_jsx(View, { style: s.dots, children: marking.dots.slice(0, 3).map((color, i) => (_jsx(View, { style: [s.dot, { backgroundColor: selected ? c.onPrimary : color }, slots?.dot] }, i))) })) : null] }))] })) }));
220
+ });
221
+ function QuickGrid({ cells, theme, slots, height }) {
222
+ const rows = [cells.slice(0, 3), cells.slice(3, 6), cells.slice(6, 9), cells.slice(9, 12)];
223
+ return (_jsx(View, { style: { height, justifyContent: 'space-evenly', paddingHorizontal: theme.spacing.sm }, children: rows.map((row, i) => (_jsx(View, { style: s.week, children: row.map((c) => (_jsx(Pressable, { onPress: c.disabled ? undefined : c.onPress, style: ({ pressed }) => [
224
+ s.quick,
225
+ {
226
+ borderRadius: theme.radius.full,
227
+ backgroundColor: c.selected ? theme.colors.primary : pressed ? theme.colors.surface : 'transparent',
228
+ },
229
+ slots?.gridCell,
230
+ ], accessibilityRole: "button", accessibilityState: { selected: c.selected, disabled: c.disabled }, children: _jsx(Text, { style: [
231
+ textStyle(theme, 'md', c.selected ? 'bold' : 'regular'),
232
+ { color: c.selected ? theme.colors.onPrimary : c.disabled ? theme.colors.textDisabled : theme.colors.text },
233
+ slots?.gridCellText,
234
+ ], children: c.label }) }, c.key))) }, i))) }));
235
+ }
236
+ const s = StyleSheet.create({
237
+ header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
238
+ titleBtn: { flexDirection: 'row', alignItems: 'center', paddingVertical: 6, paddingHorizontal: 8 },
239
+ arrows: { flexDirection: 'row', gap: 4 },
240
+ arrow: { width: 36, height: 36, alignItems: 'center', justifyContent: 'center' },
241
+ week: { flexDirection: 'row' },
242
+ cell: { flex: 1, alignItems: 'center', justifyContent: 'center' },
243
+ dayInner: { alignItems: 'center', justifyContent: 'center' },
244
+ band: { position: 'absolute', start: 0, end: 0 },
245
+ dots: { flexDirection: 'row', gap: 2, position: 'absolute', bottom: 4 },
246
+ dot: { width: 4, height: 4, borderRadius: 2 },
247
+ quick: { flex: 1, marginHorizontal: 6, height: 44, alignItems: 'center', justifyContent: 'center' },
248
+ });
249
+ //# sourceMappingURL=Calendar.js.map