react-native-calendar-resource 1.0.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 +403 -0
- package/dist/components/calendar/Calendar.d.ts +3 -0
- package/dist/components/calendar/Calendar.d.ts.map +1 -0
- package/dist/components/calendar/Calendar.js +69 -0
- package/dist/components/calendar/CalendarHeader.d.ts +14 -0
- package/dist/components/calendar/CalendarHeader.d.ts.map +1 -0
- package/dist/components/calendar/CalendarHeader.js +113 -0
- package/dist/components/calendar/EventsLayer.d.ts +13 -0
- package/dist/components/calendar/EventsLayer.d.ts.map +1 -0
- package/dist/components/calendar/EventsLayer.js +79 -0
- package/dist/components/calendar/GridBody.d.ts +14 -0
- package/dist/components/calendar/GridBody.d.ts.map +1 -0
- package/dist/components/calendar/GridBody.js +70 -0
- package/dist/components/calendar/ResourceHeaders.d.ts +13 -0
- package/dist/components/calendar/ResourceHeaders.d.ts.map +1 -0
- package/dist/components/calendar/ResourceHeaders.js +32 -0
- package/dist/components/calendar/TimeColumn.d.ts +14 -0
- package/dist/components/calendar/TimeColumn.d.ts.map +1 -0
- package/dist/components/calendar/TimeColumn.js +56 -0
- package/dist/components/calendar/UnavailableLayer.d.ts +12 -0
- package/dist/components/calendar/UnavailableLayer.d.ts.map +1 -0
- package/dist/components/calendar/UnavailableLayer.js +122 -0
- package/dist/components/calendar/dateUtils.d.ts +37 -0
- package/dist/components/calendar/dateUtils.d.ts.map +1 -0
- package/dist/components/calendar/dateUtils.js +128 -0
- package/dist/components/calendar/index.d.ts +9 -0
- package/dist/components/calendar/index.d.ts.map +1 -0
- package/dist/components/calendar/index.js +7 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/types/calendar.d.ts +118 -0
- package/dist/types/calendar.d.ts.map +1 -0
- package/dist/types/calendar.js +1 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 [Your Name]
|
|
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
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# React Native Calendar Component
|
|
2
|
+
|
|
3
|
+
A fully customizable, type-safe resource-based calendar component for React Native. Built with TypeScript and pure React Native styling, this calendar displays events across multiple resources (courts, rooms, equipment, etc.) with flexible time slots and unavailable periods.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✨ **Fully Customizable** - Configure everything from dimensions to colors to rendering
|
|
8
|
+
- 🎯 **Type-Safe** - Full TypeScript support with generics
|
|
9
|
+
- 📱 **Resource-Based** - Display multiple resources side-by-side
|
|
10
|
+
- ⏰ **Flexible Time Slots** - Configure any start/end hour range
|
|
11
|
+
- 🎨 **Custom Rendering** - Override default renderers for events, resources, and slots
|
|
12
|
+
- 🚫 **Unavailable Slots** - Support for reserved, off-hours, and disabled periods
|
|
13
|
+
- 📐 **Configurable Dimensions** - Adjust resource width and hour height
|
|
14
|
+
- 🎭 **Style Customization** - Full control over colors and appearance
|
|
15
|
+
- 👆 **Interactive** - Handle slot, event, and resource press events
|
|
16
|
+
- 🔄 **Synchronized Scrolling** - Header and time column scroll with the grid
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install react-native-calendar-resource
|
|
24
|
+
# or
|
|
25
|
+
yarn add react-native-calendar-resource
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Dependencies
|
|
29
|
+
|
|
30
|
+
Ensure you have the required peer dependencies:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install react react-native date-fns
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { Calendar } from "react-native-calendar-resource";
|
|
42
|
+
|
|
43
|
+
function MyCalendar() {
|
|
44
|
+
return (
|
|
45
|
+
<Calendar
|
|
46
|
+
date={new Date()}
|
|
47
|
+
resources={[
|
|
48
|
+
{ id: "1", label: "event 1" },
|
|
49
|
+
{ id: "2", label: "event 2" },
|
|
50
|
+
]}
|
|
51
|
+
events={[
|
|
52
|
+
{
|
|
53
|
+
id: "evt1",
|
|
54
|
+
title: "Booked",
|
|
55
|
+
resourceId: "1",
|
|
56
|
+
startHour: 10,
|
|
57
|
+
endHour: 12,
|
|
58
|
+
color: "#3b82f6",
|
|
59
|
+
},
|
|
60
|
+
]}
|
|
61
|
+
onSlotPress={(hour, resourceId, date) => {
|
|
62
|
+
console.log("Slot pressed:", { hour, resourceId, date });
|
|
63
|
+
}}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## API Reference
|
|
72
|
+
|
|
73
|
+
### Required Props
|
|
74
|
+
|
|
75
|
+
| Prop | Type | Description |
|
|
76
|
+
| ----------- | -------------------- | ---------------------------------------- |
|
|
77
|
+
| `date` | `Date` | The date to display in the calendar |
|
|
78
|
+
| `resources` | `Resource<T>[]` | Array of resources to display as columns |
|
|
79
|
+
| `events` | `CalendarEvent<T>[]` | Array of events to render |
|
|
80
|
+
|
|
81
|
+
### Optional Props
|
|
82
|
+
|
|
83
|
+
| Prop | Type | Default | Description |
|
|
84
|
+
| ------------------------ | ---------------------------------- | ---------------------------------------------------- | -------------------------------------- |
|
|
85
|
+
| `unavailableSlots` | `UnavailableSlot<T>[]` | `[]` | Slots that are unavailable |
|
|
86
|
+
| `timeConfig` | `CalendarTimeConfig` | `{ startHour: 9, endHour: 24, timeFormat: "HH:mm" }` | Time configuration |
|
|
87
|
+
| `dimensions` | `Partial<CalendarDimensions>` | `{ resourceWidth: 64, hourHeight: 64 }` | Grid dimensions |
|
|
88
|
+
| `styles` | `CalendarStyles` | - | Overall calendar styling |
|
|
89
|
+
| `eventStyles` | `CalendarEventStyles` | - | Event-specific styling |
|
|
90
|
+
| `unavailableStyles` | `UnavailableSlotStyles` | - | Unavailable slot styling |
|
|
91
|
+
| `onSlotPress` | `(hour, resourceId, date) => void` | - | Called when empty slot is pressed |
|
|
92
|
+
| `onEventPress` | `(event) => void` | - | Called when event is pressed |
|
|
93
|
+
| `onResourcePress` | `(resource) => void` | - | Called when resource header is pressed |
|
|
94
|
+
| `renderEvent` | `(event, dimensions) => ReactNode` | - | Custom event renderer |
|
|
95
|
+
| `renderResourceHeader` | `(resource) => ReactNode` | - | Custom resource header renderer |
|
|
96
|
+
| `renderTimeSlot` | `(hour) => ReactNode` | - | Custom time slot renderer |
|
|
97
|
+
| `renderUnavailableSlot` | `(slot, dimensions) => ReactNode` | - | Custom unavailable slot renderer |
|
|
98
|
+
| `showHeader` | `boolean` | `true` | Show date header |
|
|
99
|
+
| `showTimeColumn` | `boolean` | `true` | Show time column |
|
|
100
|
+
| `showResourceHeaders` | `boolean` | `true` | Show resource headers |
|
|
101
|
+
| `enableHorizontalScroll` | `boolean` | `true` | Enable horizontal scrolling |
|
|
102
|
+
| `enableVerticalScroll` | `boolean` | `true` | Enable vertical scrolling |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Type Definitions
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
type Resource<T = any> = {
|
|
110
|
+
id: string;
|
|
111
|
+
label: string;
|
|
112
|
+
data?: T; // Additional custom data
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
type CalendarEvent<T = any> = {
|
|
116
|
+
id: string;
|
|
117
|
+
title: string;
|
|
118
|
+
resourceId: string;
|
|
119
|
+
startHour: number;
|
|
120
|
+
endHour: number;
|
|
121
|
+
color?: string;
|
|
122
|
+
data?: T; // Additional custom data
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
type UnavailableSlot<T = any> = {
|
|
126
|
+
id: string;
|
|
127
|
+
resourceId: string;
|
|
128
|
+
startHour: number;
|
|
129
|
+
endHour: number;
|
|
130
|
+
type: "reserved" | "offHours" | "disabled";
|
|
131
|
+
data?: T; // Additional custom data
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
type CalendarTimeConfig = {
|
|
135
|
+
startHour: number; // 0-23
|
|
136
|
+
endHour: number; // 1-24
|
|
137
|
+
timeFormat?: string; // date-fns format string
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
type CalendarDimensions = {
|
|
141
|
+
resourceWidth: number; // px
|
|
142
|
+
hourHeight: number; // px
|
|
143
|
+
};
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Examples
|
|
149
|
+
|
|
150
|
+
### Custom Time Range
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
<Calendar
|
|
154
|
+
date={new Date()}
|
|
155
|
+
resources={resources}
|
|
156
|
+
events={events}
|
|
157
|
+
timeConfig={{
|
|
158
|
+
startHour: 6, // Start at 6 AM
|
|
159
|
+
endHour: 22, // End at 10 PM
|
|
160
|
+
timeFormat: "h:mm a", // 12-hour format
|
|
161
|
+
}}
|
|
162
|
+
/>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Custom Dimensions
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
<Calendar
|
|
169
|
+
date={new Date()}
|
|
170
|
+
resources={resources}
|
|
171
|
+
events={events}
|
|
172
|
+
dimensions={{
|
|
173
|
+
resourceWidth: 100,
|
|
174
|
+
hourHeight: 80,
|
|
175
|
+
}}
|
|
176
|
+
/>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Custom Event Rendering
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
<Calendar
|
|
183
|
+
date={new Date()}
|
|
184
|
+
resources={resources}
|
|
185
|
+
events={events}
|
|
186
|
+
renderEvent={(event, { width, height }) => (
|
|
187
|
+
<View style={{ width, height, backgroundColor: event.color, padding: 8 }}>
|
|
188
|
+
<Text style={{ color: "white", fontWeight: "bold" }}>{event.title}</Text>
|
|
189
|
+
<Text style={{ color: "white", fontSize: 12 }}>
|
|
190
|
+
{event.startHour}:00 - {event.endHour}:00
|
|
191
|
+
</Text>
|
|
192
|
+
</View>
|
|
193
|
+
)}
|
|
194
|
+
/>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Custom Resource Headers
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
<Calendar
|
|
201
|
+
date={new Date()}
|
|
202
|
+
resources={resources}
|
|
203
|
+
events={events}
|
|
204
|
+
renderResourceHeader={(resource) => (
|
|
205
|
+
<View style={{ padding: 8 }}>
|
|
206
|
+
<Text style={{ fontSize: 16, fontWeight: "bold", color: "#fff" }}>
|
|
207
|
+
{resource.label}
|
|
208
|
+
</Text>
|
|
209
|
+
<Text style={{ fontSize: 12, color: "#999" }}>Available</Text>
|
|
210
|
+
</View>
|
|
211
|
+
)}
|
|
212
|
+
/>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Custom Styling
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
<Calendar
|
|
219
|
+
date={new Date()}
|
|
220
|
+
resources={resources}
|
|
221
|
+
events={events}
|
|
222
|
+
styles={{
|
|
223
|
+
backgroundColor: "#000",
|
|
224
|
+
gridColor: "#333",
|
|
225
|
+
slotBackgroundColor: "#1a1a1a",
|
|
226
|
+
}}
|
|
227
|
+
eventStyles={{
|
|
228
|
+
defaultColor: "#3b82f6",
|
|
229
|
+
borderRadius: 12,
|
|
230
|
+
opacity: 0.9,
|
|
231
|
+
}}
|
|
232
|
+
unavailableStyles={{
|
|
233
|
+
reserved: {
|
|
234
|
+
backgroundColor: "#dc2626",
|
|
235
|
+
opacity: 0.4,
|
|
236
|
+
},
|
|
237
|
+
offHours: {
|
|
238
|
+
backgroundColor: "#404040",
|
|
239
|
+
opacity: 0.6,
|
|
240
|
+
},
|
|
241
|
+
}}
|
|
242
|
+
/>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### With TypeScript Generics
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
interface eventData {
|
|
249
|
+
capacity: number;
|
|
250
|
+
amenities: string[];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
interface BookingData {
|
|
254
|
+
userId: string;
|
|
255
|
+
notes: string;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
<Calendar<BookingData, eventData>
|
|
259
|
+
date={new Date()}
|
|
260
|
+
resources={[
|
|
261
|
+
{
|
|
262
|
+
id: "1",
|
|
263
|
+
label: "event 1",
|
|
264
|
+
data: { capacity: 4, amenities: ["lights", "net"] },
|
|
265
|
+
},
|
|
266
|
+
]}
|
|
267
|
+
events={[
|
|
268
|
+
{
|
|
269
|
+
id: "1",
|
|
270
|
+
title: "Booked",
|
|
271
|
+
resourceId: "1",
|
|
272
|
+
startHour: 10,
|
|
273
|
+
endHour: 12,
|
|
274
|
+
data: { userId: "user123", notes: "Bring equipment" },
|
|
275
|
+
},
|
|
276
|
+
]}
|
|
277
|
+
onEventPress={(event) => {
|
|
278
|
+
console.log(event.data.userId); // Type-safe!
|
|
279
|
+
}}
|
|
280
|
+
/>;
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Technologies
|
|
286
|
+
|
|
287
|
+
- React Native
|
|
288
|
+
- Expo
|
|
289
|
+
- TypeScript
|
|
290
|
+
- date-fns
|
|
291
|
+
- NativeWind (optional, for default styling)
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## License
|
|
296
|
+
|
|
297
|
+
MIT
|
|
298
|
+
|
|
299
|
+
- The tab bar remains visible while navigating deeper into Explore-related screens
|
|
300
|
+
- Navigation context is preserved
|
|
301
|
+
- Platform-native gestures and transitions are maintained
|
|
302
|
+
|
|
303
|
+
Headers are disabled at the stack level and selectively enabled per screen to match the Figma design and avoid default iOS -Liquid Glass- system back button styling.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
### Calendar Component
|
|
308
|
+
|
|
309
|
+
The booking calendar was implemented from scratch instead of relying on third-party libraries.
|
|
310
|
+
|
|
311
|
+
Reasons for this decision:
|
|
312
|
+
|
|
313
|
+
1. No third-party library was found that met the requirements of the project.
|
|
314
|
+
2. Pixel-perfect alignment with the design
|
|
315
|
+
3. Full control over a resource-based grid (events × time)
|
|
316
|
+
4. Optimized performance for the specific use case
|
|
317
|
+
5. Reduced dependency and bundle size overhead
|
|
318
|
+
|
|
319
|
+
#### Calendar Architecture
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
Calendar/
|
|
323
|
+
├── Calendar.tsx
|
|
324
|
+
├── CalendarHeader.tsx
|
|
325
|
+
├── TimeColumn.tsx
|
|
326
|
+
├── ResourceHeaders.tsx
|
|
327
|
+
├── GridBody.tsx
|
|
328
|
+
├── EventsLayer.tsx
|
|
329
|
+
└── UnavailableLayer.tsx
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
#### Key Features
|
|
333
|
+
|
|
334
|
+
- Synchronized horizontal and vertical scrolling
|
|
335
|
+
- Resource-based layout for multiple events
|
|
336
|
+
- Three slot states: available, booked, unavailable
|
|
337
|
+
- Configurable time range, slot height, and column width
|
|
338
|
+
- Cross-platform behavior parity (iOS & Android)
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
### Styling Approach
|
|
343
|
+
|
|
344
|
+
Styling is handled using NativeWind (TailwindCSS for React Native):
|
|
345
|
+
|
|
346
|
+
- Semantic color tokens (background, card, accent, muted)
|
|
347
|
+
- Consistent spacing and typography via utility classes
|
|
348
|
+
- Centralized theme configuration
|
|
349
|
+
- Custom fonts (Asap, Bebas Neue) loaded via `expo-font`
|
|
350
|
+
- A mix of @expo/vector-icons and SVG icon components was used. SVGs were introduced for icons where an exact visual match was not available in the Expo icon set.
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
### Date & Time Handling
|
|
355
|
+
|
|
356
|
+
All date and time operations use date-fns for:
|
|
357
|
+
|
|
358
|
+
- Clear and readable date manipulation
|
|
359
|
+
- Tree-shakeable imports
|
|
360
|
+
- Safer alternatives to native Date APIs
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Tech Stack
|
|
365
|
+
|
|
366
|
+
- Expo SDK 54
|
|
367
|
+
- TypeScript
|
|
368
|
+
- Expo Router + React Navigation Stack
|
|
369
|
+
- NativeWind (TailwindCSS)
|
|
370
|
+
- date-fns
|
|
371
|
+
- react-native-leaflet-view (Leaflet maps via WebView)
|
|
372
|
+
- expo-linear-gradient
|
|
373
|
+
- react-native-reanimated
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## Implemented Features
|
|
378
|
+
|
|
379
|
+
- Venue details screen with image carousel
|
|
380
|
+
- Rating badges and amenity tags
|
|
381
|
+
- Coach cards with detail navigation
|
|
382
|
+
- Custom calendar grid
|
|
383
|
+
- Date shortcuts (Today / Tomorrow)
|
|
384
|
+
- Visual distinction between slot states
|
|
385
|
+
- Location card with map preview
|
|
386
|
+
- Persistent tab bar navigation
|
|
387
|
+
- Nested stack navigation within Explore
|
|
388
|
+
- Cross-platform support (iOS & Android)
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Additional Notes
|
|
393
|
+
|
|
394
|
+
- The Explore tab was used to display event details since no design was provided for the Home screen.
|
|
395
|
+
In a real-world application, the Explore tab would typically be used to search for or browse events, then navigate to the event details screen.
|
|
396
|
+
- Non-Explore tabs are intentionally minimal as they are outside the task scope
|
|
397
|
+
- Leaflet (via `react-native-leaflet-view`) was used for maps instead of `react-native-maps` or other Google Maps-based libraries, as those require a Google Cloud API key and billing subscription
|
|
398
|
+
- Mock data is used throughout the app
|
|
399
|
+
- Safe area insets are handled for iOS notch and home indicator
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
**Author:** Abdelrahman Sobhy
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { CalendarProps } from "../../types/calendar";
|
|
2
|
+
export declare function Calendar<TEvent = any, TResource = any, TUnavailable = any>({ date, resources, events, unavailableSlots, timeConfig, dimensions, styles, eventStyles, unavailableStyles, onSlotPress, onEventPress, onResourcePress, onDateChange, minDate, maxDate, allowPastDates, renderEvent, renderResourceHeader, renderTimeSlot, renderUnavailableSlot, showHeader, showTimeColumn, showResourceHeaders, enableHorizontalScroll, enableVerticalScroll, showDateNavigation, dateFormat, accessibilityLabel, }: CalendarProps<TEvent, TResource, TUnavailable>): import("react").JSX.Element;
|
|
3
|
+
//# sourceMappingURL=Calendar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Calendar.d.ts","sourceRoot":"","sources":["../../../components/calendar/Calendar.tsx"],"names":[],"mappings":"AAOA,OAAO,EAEL,aAAa,EAEd,MAAM,sBAAsB,CAAC;AAoB9B,wBAAgB,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG,EAAE,YAAY,GAAG,GAAG,EAAE,EAC1E,IAAI,EACJ,SAAS,EACT,MAAM,EACN,gBAAqB,EACrB,UAAU,EACV,UAAU,EACV,MAAM,EACN,WAAW,EACX,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,OAAO,EACP,OAAO,EACP,cAAsB,EACtB,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,UAAiB,EACjB,cAAqB,EACrB,mBAA0B,EAC1B,sBAA6B,EAC7B,oBAA2B,EAC3B,kBAAyB,EACzB,UAA+B,EAC/B,kBAA+B,GAChC,EAAE,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,YAAY,CAAC,+BA0IhD"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useMemo, useRef } from "react";
|
|
2
|
+
import { ScrollView, View, } from "react-native";
|
|
3
|
+
import { CalendarHeader } from "./CalendarHeader";
|
|
4
|
+
import { EventsLayer } from "./EventsLayer";
|
|
5
|
+
import { GridBody } from "./GridBody";
|
|
6
|
+
import { ResourceHeaders } from "./ResourceHeaders";
|
|
7
|
+
import { TimeColumn } from "./TimeColumn";
|
|
8
|
+
import { UnavailableLayer } from "./UnavailableLayer";
|
|
9
|
+
// Default configuration
|
|
10
|
+
const DEFAULT_DIMENSIONS = {
|
|
11
|
+
resourceWidth: 64,
|
|
12
|
+
hourHeight: 64,
|
|
13
|
+
};
|
|
14
|
+
const DEFAULT_TIME_CONFIG = {
|
|
15
|
+
startHour: 9,
|
|
16
|
+
endHour: 24,
|
|
17
|
+
timeFormat: "HH:mm",
|
|
18
|
+
};
|
|
19
|
+
export function Calendar({ date, resources, events, unavailableSlots = [], timeConfig, dimensions, styles, eventStyles, unavailableStyles, onSlotPress, onEventPress, onResourcePress, onDateChange, minDate, maxDate, allowPastDates = false, renderEvent, renderResourceHeader, renderTimeSlot, renderUnavailableSlot, showHeader = true, showTimeColumn = true, showResourceHeaders = true, enableHorizontalScroll = true, enableVerticalScroll = true, showDateNavigation = true, dateFormat = "EEE, MMM d, yyyy", accessibilityLabel = "Calendar", }) {
|
|
20
|
+
const headerScrollRef = useRef(null);
|
|
21
|
+
const timeColumnScrollRef = useRef(null);
|
|
22
|
+
// Merge configurations with defaults
|
|
23
|
+
const finalDimensions = useMemo(() => ({ ...DEFAULT_DIMENSIONS, ...dimensions }), [dimensions]);
|
|
24
|
+
const finalTimeConfig = useMemo(() => ({ ...DEFAULT_TIME_CONFIG, ...timeConfig }), [timeConfig]);
|
|
25
|
+
// Calculate grid dimensions
|
|
26
|
+
const gridTotalWidth = resources.length * finalDimensions.resourceWidth;
|
|
27
|
+
const totalHours = finalTimeConfig.endHour - finalTimeConfig.startHour;
|
|
28
|
+
const gridTotalHeight = totalHours * finalDimensions.hourHeight;
|
|
29
|
+
const handleHorizontalScroll = (scrollEvent) => {
|
|
30
|
+
const horizontalOffset = scrollEvent.nativeEvent.contentOffset.x;
|
|
31
|
+
headerScrollRef.current?.scrollTo({ x: horizontalOffset, animated: false });
|
|
32
|
+
};
|
|
33
|
+
const handleVerticalScroll = (scrollEvent) => {
|
|
34
|
+
const verticalOffset = scrollEvent.nativeEvent.contentOffset.y;
|
|
35
|
+
timeColumnScrollRef.current?.scrollTo({
|
|
36
|
+
y: verticalOffset,
|
|
37
|
+
animated: false,
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
const backgroundColor = styles?.backgroundColor || "#1a1a1a";
|
|
41
|
+
return (<View style={{ flex: 1, backgroundColor }} accessibilityLabel={accessibilityLabel}>
|
|
42
|
+
{showHeader && (<CalendarHeader date={date} timeConfig={finalTimeConfig} onDateChange={onDateChange} showDateNavigation={showDateNavigation} dateFormat={dateFormat} minDate={minDate} maxDate={maxDate} allowPastDates={allowPastDates}/>)}
|
|
43
|
+
|
|
44
|
+
{showResourceHeaders && (<ResourceHeaders resources={resources} scrollRef={headerScrollRef} dimensions={finalDimensions} renderResourceHeader={renderResourceHeader} onResourcePress={onResourcePress}/>)}
|
|
45
|
+
|
|
46
|
+
<View style={{ flex: 1, flexDirection: "row" }}>
|
|
47
|
+
{showTimeColumn && (<TimeColumn scrollRef={timeColumnScrollRef} gridTotalHeight={gridTotalHeight} timeConfig={finalTimeConfig} dimensions={finalDimensions} renderTimeSlot={renderTimeSlot} styles={styles}/>)}
|
|
48
|
+
|
|
49
|
+
<View style={{ flex: 1 }}>
|
|
50
|
+
<ScrollView horizontal={enableHorizontalScroll} showsHorizontalScrollIndicator={enableHorizontalScroll} bounces={false} onScroll={handleHorizontalScroll} scrollEventThrottle={16} nestedScrollEnabled={true} scrollEnabled={enableHorizontalScroll}>
|
|
51
|
+
<ScrollView showsVerticalScrollIndicator={enableVerticalScroll} bounces={false} onScroll={handleVerticalScroll} scrollEventThrottle={16} nestedScrollEnabled={true} scrollEnabled={enableVerticalScroll} contentContainerStyle={{
|
|
52
|
+
width: gridTotalWidth,
|
|
53
|
+
height: gridTotalHeight,
|
|
54
|
+
}}>
|
|
55
|
+
<View style={{
|
|
56
|
+
position: "relative",
|
|
57
|
+
width: gridTotalWidth,
|
|
58
|
+
height: gridTotalHeight,
|
|
59
|
+
}}>
|
|
60
|
+
<GridBody resources={resources} events={events} unavailableSlots={unavailableSlots} onSlotPress={onSlotPress} date={date} timeConfig={finalTimeConfig} dimensions={finalDimensions} styles={styles}/>
|
|
61
|
+
<UnavailableLayer unavailableSlots={unavailableSlots} resources={resources} timeConfig={finalTimeConfig} dimensions={finalDimensions} unavailableStyles={unavailableStyles} renderUnavailableSlot={renderUnavailableSlot}/>
|
|
62
|
+
<EventsLayer events={events} resources={resources} onEventPress={onEventPress} timeConfig={finalTimeConfig} dimensions={finalDimensions} eventStyles={eventStyles} renderEvent={renderEvent}/>
|
|
63
|
+
</View>
|
|
64
|
+
</ScrollView>
|
|
65
|
+
</ScrollView>
|
|
66
|
+
</View>
|
|
67
|
+
</View>
|
|
68
|
+
</View>);
|
|
69
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CalendarTimeConfig } from "../../types/calendar";
|
|
2
|
+
type CalendarHeaderProps = {
|
|
3
|
+
date: Date;
|
|
4
|
+
timeConfig: CalendarTimeConfig;
|
|
5
|
+
onDateChange?: (date: Date) => void;
|
|
6
|
+
showDateNavigation?: boolean;
|
|
7
|
+
dateFormat?: string;
|
|
8
|
+
minDate?: Date;
|
|
9
|
+
maxDate?: Date;
|
|
10
|
+
allowPastDates?: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare function CalendarHeader({ date, timeConfig, onDateChange, showDateNavigation, dateFormat, minDate, maxDate, allowPastDates, }: CalendarHeaderProps): import("react").JSX.Element;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=CalendarHeader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CalendarHeader.d.ts","sourceRoot":"","sources":["../../../components/calendar/CalendarHeader.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,KAAK,mBAAmB,GAAG;IACzB,IAAI,EAAE,IAAI,CAAC;IACX,UAAU,EAAE,kBAAkB,CAAC;IAC/B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACpC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,UAAU,EACV,YAAY,EACZ,kBAAyB,EACzB,UAA+B,EAC/B,OAAO,EACP,OAAO,EACP,cAAqB,GACtB,EAAE,mBAAmB,+BA4IrB"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { MaterialIcons } from "@expo/vector-icons";
|
|
2
|
+
import { addDays, format, subDays } from "date-fns";
|
|
3
|
+
import { Pressable, Text, View } from "react-native";
|
|
4
|
+
export function CalendarHeader({ date, timeConfig, onDateChange, showDateNavigation = true, dateFormat = "EEE, MMM d, yyyy", minDate, maxDate, allowPastDates = true, }) {
|
|
5
|
+
const handlePreviousDay = () => {
|
|
6
|
+
const newDate = subDays(date, 1);
|
|
7
|
+
// Check if going back is allowed
|
|
8
|
+
if (!allowPastDates) {
|
|
9
|
+
const today = new Date();
|
|
10
|
+
today.setHours(0, 0, 0, 0);
|
|
11
|
+
const checkDate = new Date(newDate);
|
|
12
|
+
checkDate.setHours(0, 0, 0, 0);
|
|
13
|
+
if (checkDate < today)
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
// Check minDate constraint
|
|
17
|
+
if (minDate) {
|
|
18
|
+
const min = new Date(minDate);
|
|
19
|
+
min.setHours(0, 0, 0, 0);
|
|
20
|
+
const checkDate = new Date(newDate);
|
|
21
|
+
checkDate.setHours(0, 0, 0, 0);
|
|
22
|
+
if (checkDate < min)
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
onDateChange?.(newDate);
|
|
26
|
+
};
|
|
27
|
+
const handleNextDay = () => {
|
|
28
|
+
const newDate = addDays(date, 1);
|
|
29
|
+
// Check maxDate constraint
|
|
30
|
+
if (maxDate) {
|
|
31
|
+
const max = new Date(maxDate);
|
|
32
|
+
max.setHours(0, 0, 0, 0);
|
|
33
|
+
const checkDate = new Date(newDate);
|
|
34
|
+
checkDate.setHours(0, 0, 0, 0);
|
|
35
|
+
if (checkDate > max)
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
onDateChange?.(newDate);
|
|
39
|
+
};
|
|
40
|
+
// Check if previous button should be disabled
|
|
41
|
+
const isPreviousDisabled = () => {
|
|
42
|
+
const prevDate = subDays(date, 1);
|
|
43
|
+
if (!allowPastDates) {
|
|
44
|
+
const today = new Date();
|
|
45
|
+
today.setHours(0, 0, 0, 0);
|
|
46
|
+
const checkDate = new Date(prevDate);
|
|
47
|
+
checkDate.setHours(0, 0, 0, 0);
|
|
48
|
+
if (checkDate < today)
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (minDate) {
|
|
52
|
+
const min = new Date(minDate);
|
|
53
|
+
min.setHours(0, 0, 0, 0);
|
|
54
|
+
const checkDate = new Date(prevDate);
|
|
55
|
+
checkDate.setHours(0, 0, 0, 0);
|
|
56
|
+
if (checkDate < min)
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
};
|
|
61
|
+
// Check if next button should be disabled
|
|
62
|
+
const isNextDisabled = () => {
|
|
63
|
+
if (!maxDate)
|
|
64
|
+
return false;
|
|
65
|
+
const nextDate = addDays(date, 1);
|
|
66
|
+
const max = new Date(maxDate);
|
|
67
|
+
max.setHours(0, 0, 0, 0);
|
|
68
|
+
const checkDate = new Date(nextDate);
|
|
69
|
+
checkDate.setHours(0, 0, 0, 0);
|
|
70
|
+
return checkDate > max;
|
|
71
|
+
};
|
|
72
|
+
const prevDisabled = isPreviousDisabled();
|
|
73
|
+
const nextDisabled = isNextDisabled();
|
|
74
|
+
return (<View style={{
|
|
75
|
+
backgroundColor: "#1a1a1a",
|
|
76
|
+
paddingHorizontal: 20,
|
|
77
|
+
paddingVertical: 16,
|
|
78
|
+
}}>
|
|
79
|
+
<View style={{
|
|
80
|
+
flexDirection: "row",
|
|
81
|
+
alignItems: "center",
|
|
82
|
+
justifyContent: "space-between",
|
|
83
|
+
}}>
|
|
84
|
+
{showDateNavigation ? (<>
|
|
85
|
+
<Pressable onPress={handlePreviousDay} disabled={prevDisabled} style={{
|
|
86
|
+
width: 40,
|
|
87
|
+
height: 40,
|
|
88
|
+
borderRadius: 20,
|
|
89
|
+
alignItems: "center",
|
|
90
|
+
justifyContent: "center",
|
|
91
|
+
backgroundColor: "rgba(38, 38, 38, 0.75)",
|
|
92
|
+
}}>
|
|
93
|
+
<MaterialIcons name="chevron-left" size={20} color={prevDisabled ? "#626262" : "#fefefe"}/>
|
|
94
|
+
</Pressable>
|
|
95
|
+
<Text style={{ color: "#fefefe", fontSize: 16, fontWeight: "600" }}>
|
|
96
|
+
{format(date, dateFormat)}
|
|
97
|
+
</Text>
|
|
98
|
+
<Pressable onPress={handleNextDay} disabled={nextDisabled} style={{
|
|
99
|
+
width: 40,
|
|
100
|
+
height: 40,
|
|
101
|
+
borderRadius: 20,
|
|
102
|
+
backgroundColor: "rgba(38, 38, 38, 0.75)",
|
|
103
|
+
alignItems: "center",
|
|
104
|
+
justifyContent: "center",
|
|
105
|
+
}}>
|
|
106
|
+
<MaterialIcons name="chevron-right" size={20} color={nextDisabled ? "#626262" : "#fefefe"}/>
|
|
107
|
+
</Pressable>
|
|
108
|
+
</>) : (<Text style={{ color: '#fefefe', fontSize: 16, fontWeight: '600' }}>
|
|
109
|
+
{format(date, dateFormat)}
|
|
110
|
+
</Text>)}
|
|
111
|
+
</View>
|
|
112
|
+
</View>);
|
|
113
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CalendarDimensions, CalendarEvent, CalendarEventStyles, CalendarTimeConfig, RenderEventFunction, Resource } from "../../types/calendar";
|
|
2
|
+
type EventsLayerProps<TEvent = any, TResource = any> = {
|
|
3
|
+
events: CalendarEvent<TEvent>[];
|
|
4
|
+
resources: Resource<TResource>[];
|
|
5
|
+
onEventPress?: (event: CalendarEvent<TEvent>) => void;
|
|
6
|
+
timeConfig: CalendarTimeConfig;
|
|
7
|
+
dimensions: CalendarDimensions;
|
|
8
|
+
eventStyles?: CalendarEventStyles;
|
|
9
|
+
renderEvent?: RenderEventFunction<TEvent>;
|
|
10
|
+
};
|
|
11
|
+
export declare function EventsLayer<TEvent = any, TResource = any>({ events, resources, onEventPress, timeConfig, dimensions, eventStyles, renderEvent, }: EventsLayerProps<TEvent, TResource>): import("react").JSX.Element;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=EventsLayer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventsLayer.d.ts","sourceRoot":"","sources":["../../../components/calendar/EventsLayer.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAE9B,KAAK,gBAAgB,CAAC,MAAM,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG,IAAI;IACrD,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;IAChC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;IACjC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;IACtD,UAAU,EAAE,kBAAkB,CAAC;IAC/B,UAAU,EAAE,kBAAkB,CAAC;IAC/B,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,WAAW,CAAC,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC;CAC3C,CAAC;AAEF,wBAAgB,WAAW,CAAC,MAAM,GAAG,GAAG,EAAE,SAAS,GAAG,GAAG,EAAE,EACzD,MAAM,EACN,SAAS,EACT,YAAY,EACZ,UAAU,EACV,UAAU,EACV,WAAW,EACX,WAAW,GACZ,EAAE,gBAAgB,CAAC,MAAM,EAAE,SAAS,CAAC,+BA+GrC"}
|