react-native-calendar-resource 1.0.2 → 1.1.2
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 +1 -1
- package/README.md +126 -27
- package/dist/components/calendar/Calendar.d.ts +1 -1
- package/dist/components/calendar/Calendar.d.ts.map +1 -1
- package/dist/components/calendar/Calendar.js +108 -13
- package/dist/components/calendar/CalendarHeader.d.ts.map +1 -1
- package/dist/components/calendar/CalendarHeader.js +2 -3
- package/dist/components/calendar/EventsLayer.d.ts +9 -7
- package/dist/components/calendar/EventsLayer.d.ts.map +1 -1
- package/dist/components/calendar/EventsLayer.js +185 -37
- package/dist/components/calendar/GridBody.d.ts +5 -5
- package/dist/components/calendar/GridBody.d.ts.map +1 -1
- package/dist/components/calendar/GridBody.js +4 -3
- package/dist/components/calendar/ResourceHeaders.d.ts +5 -5
- package/dist/components/calendar/ResourceHeaders.d.ts.map +1 -1
- package/dist/components/calendar/UnavailableLayer.d.ts +5 -5
- package/dist/components/calendar/UnavailableLayer.d.ts.map +1 -1
- package/dist/components/calendar/ZoomControls.d.ts +10 -0
- package/dist/components/calendar/ZoomControls.d.ts.map +1 -0
- package/dist/components/calendar/ZoomControls.js +63 -0
- package/dist/types/calendar.d.ts +28 -18
- package/dist/types/calendar.d.ts.map +1 -1
- package/package.json +19 -4
- package/dist/components/calendar/dateUtils.d.ts +0 -37
- package/dist/components/calendar/dateUtils.d.ts.map +0 -1
- package/dist/components/calendar/dateUtils.js +0 -128
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ A fully customizable, type-safe resource-based calendar component for React Nati
|
|
|
14
14
|
- 🎭 **Style Customization** - Full control over colors and appearance
|
|
15
15
|
- 👆 **Interactive** - Handle slot, event, and resource press events
|
|
16
16
|
- 🔄 **Synchronized Scrolling** - Header and time column scroll with the grid
|
|
17
|
+
- 🎯 **Drag and Drop** - Long-press and drag events to reschedule (optional)
|
|
18
|
+
- 🔍 **Pinch to Zoom** - Zoom in/out with focal point support (optional)
|
|
17
19
|
|
|
18
20
|
---
|
|
19
21
|
|
|
@@ -30,9 +32,13 @@ yarn add react-native-calendar-resource
|
|
|
30
32
|
Ensure you have the required peer dependencies:
|
|
31
33
|
|
|
32
34
|
```bash
|
|
33
|
-
npm install
|
|
35
|
+
npm install date-fns
|
|
36
|
+
# or
|
|
37
|
+
yarn add date-fns
|
|
34
38
|
```
|
|
35
39
|
|
|
40
|
+
**Note:** For Expo projects, these are typically already installed. For bare React Native projects, follow the [react-native-gesture-handler](https://docs.swmansion.com/react-native-gesture-handler/docs/fundamentals/installation) and [react-native-reanimated](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/) installation guides.
|
|
41
|
+
|
|
36
42
|
---
|
|
37
43
|
|
|
38
44
|
## Quick Start
|
|
@@ -80,26 +86,33 @@ function MyCalendar() {
|
|
|
80
86
|
|
|
81
87
|
### Optional Props
|
|
82
88
|
|
|
83
|
-
| Prop | Type
|
|
84
|
-
| ------------------------ |
|
|
85
|
-
| `unavailableSlots` | `UnavailableSlot<T>[]`
|
|
86
|
-
| `timeConfig` | `CalendarTimeConfig`
|
|
87
|
-
| `dimensions` | `Partial<CalendarDimensions>`
|
|
88
|
-
| `styles` | `CalendarStyles`
|
|
89
|
-
| `eventStyles` | `CalendarEventStyles`
|
|
90
|
-
| `unavailableStyles` | `UnavailableSlotStyles`
|
|
91
|
-
| `onSlotPress` | `(hour, resourceId, date) => void`
|
|
92
|
-
| `onEventPress` | `(event) => void`
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
89
|
+
| Prop | Type | Default | Description |
|
|
90
|
+
| ------------------------ | -------------------------------------- | ---------------------------------------------------- | ------------------------------------------- |
|
|
91
|
+
| `unavailableSlots` | `UnavailableSlot<T>[]` | `[]` | Slots that are unavailable |
|
|
92
|
+
| `timeConfig` | `CalendarTimeConfig` | `{ startHour: 9, endHour: 24, timeFormat: "HH:mm" }` | Time configuration |
|
|
93
|
+
| `dimensions` | `Partial<CalendarDimensions>` | `{ resourceWidth: 64, hourHeight: 64 }` | Grid dimensions |
|
|
94
|
+
| `styles` | `CalendarStyles` | - | Overall calendar styling |
|
|
95
|
+
| `eventStyles` | `CalendarEventStyles` | - | Event-specific styling |
|
|
96
|
+
| `unavailableStyles` | `UnavailableSlotStyles` | - | Unavailable slot styling |
|
|
97
|
+
| `onSlotPress` | `(hour, resourceId, date) => void` | - | Called when empty slot is pressed |
|
|
98
|
+
| `onEventPress` | `(event) => void` | - | Called when event is pressed |
|
|
99
|
+
| `onEventDrop` | `(droppedData, originalEvent) => void` | - | Called when event is dropped after dragging |
|
|
100
|
+
| `onResourcePress` | `(resource) => void` | - | Called when resource header is pressed |
|
|
101
|
+
| `renderEvent` | `(event, dimensions) => ReactNode` | - | Custom event renderer |
|
|
102
|
+
| `renderResourceHeader` | `(resource) => ReactNode` | - | Custom resource header renderer |
|
|
103
|
+
| `renderTimeSlot` | `(hour) => ReactNode` | - | Custom time slot renderer |
|
|
104
|
+
| `renderUnavailableSlot` | `(slot, dimensions) => ReactNode` | - | Custom unavailable slot renderer |
|
|
105
|
+
| `showHeader` | `boolean` | `true` | Show date header |
|
|
106
|
+
| `showTimeColumn` | `boolean` | `true` | Show time column |
|
|
107
|
+
| `showResourceHeaders` | `boolean` | `true` | Show resource headers |
|
|
108
|
+
| `enableHorizontalScroll` | `boolean` | `true` | Enable horizontal scrolling |
|
|
109
|
+
| `enableVerticalScroll` | `boolean` | `true` | Enable vertical scrolling |
|
|
110
|
+
| `enableDragAndDrop` | `boolean` | `false` | Enable drag and drop for events |
|
|
111
|
+
| `zoomEnabled` | `boolean` | `false` | Enable pinch-to-zoom gesture ⚠️ Experimental |
|
|
112
|
+
| `maxZoom` | `number` | `3` | Maximum zoom level (3 = 300%) |
|
|
113
|
+
| `initialZoom` | `number` | `1` | Initial zoom level (1 = 100%) |
|
|
114
|
+
| `snapBack` | `boolean` | `false` | Auto-return to initial zoom after gesture |
|
|
115
|
+
| `snapBackDelay` | `number` | `1000` | Delay before snap-back in milliseconds |
|
|
103
116
|
|
|
104
117
|
---
|
|
105
118
|
|
|
@@ -147,6 +160,98 @@ type CalendarDimensions = {
|
|
|
147
160
|
|
|
148
161
|
## Examples
|
|
149
162
|
|
|
163
|
+
### Drag and Drop Events
|
|
164
|
+
|
|
165
|
+
Enable event rescheduling by long-pressing and dragging:
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
import { useState } from "react";
|
|
169
|
+
import { Calendar, CalendarEvent } from "react-native-calendar-resource";
|
|
170
|
+
|
|
171
|
+
function MyCalendar() {
|
|
172
|
+
const [events, setEvents] = useState<CalendarEvent[]>([
|
|
173
|
+
{
|
|
174
|
+
id: "evt1",
|
|
175
|
+
title: "Team Meeting",
|
|
176
|
+
resourceId: "1",
|
|
177
|
+
startHour: 10,
|
|
178
|
+
endHour: 11.5,
|
|
179
|
+
color: "#3b82f6",
|
|
180
|
+
},
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<Calendar
|
|
185
|
+
date={new Date()}
|
|
186
|
+
resources={resources}
|
|
187
|
+
events={events}
|
|
188
|
+
enableDragAndDrop={true}
|
|
189
|
+
onEventDrop={(droppedData, originalEvent) => {
|
|
190
|
+
// Update event with new time/resource
|
|
191
|
+
setEvents((prev) =>
|
|
192
|
+
prev.map((event) =>
|
|
193
|
+
event.id === droppedData.eventId
|
|
194
|
+
? {
|
|
195
|
+
...event,
|
|
196
|
+
resourceId: droppedData.newResourceId,
|
|
197
|
+
startHour: droppedData.newStartHour,
|
|
198
|
+
endHour: droppedData.newEndHour,
|
|
199
|
+
}
|
|
200
|
+
: event,
|
|
201
|
+
),
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
console.log("Event moved:", {
|
|
205
|
+
from: {
|
|
206
|
+
resource: originalEvent.resourceId,
|
|
207
|
+
time: `${originalEvent.startHour}-${originalEvent.endHour}`,
|
|
208
|
+
},
|
|
209
|
+
to: {
|
|
210
|
+
resource: droppedData.newResourceId,
|
|
211
|
+
time: `${droppedData.newStartHour}-${droppedData.newEndHour}`,
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
}}
|
|
215
|
+
/>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**How it works:**
|
|
221
|
+
|
|
222
|
+
- Long-press an event for 500ms to start dragging
|
|
223
|
+
- Drag the event to a new resource or time slot
|
|
224
|
+
- Events snap to 30-minute intervals
|
|
225
|
+
- Visual feedback shows the event being dragged (opacity, scale, border)
|
|
226
|
+
- The `onEventDrop` callback receives both the new position and original event data
|
|
227
|
+
|
|
228
|
+
### Pinch to Zoom
|
|
229
|
+
|
|
230
|
+
> **⚠️ EXPERIMENTAL FEATURE**: Pinch-to-zoom is currently experimental and may have performance or gesture detection issues. We recommend waiting for a stable release before using in production.
|
|
231
|
+
|
|
232
|
+
Enable pinch-to-zoom for better visibility of events:
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
<Calendar
|
|
236
|
+
date={new Date()}
|
|
237
|
+
resources={resources}
|
|
238
|
+
events={events}
|
|
239
|
+
zoomEnabled={true}
|
|
240
|
+
maxZoom={3}
|
|
241
|
+
initialZoom={1}
|
|
242
|
+
snapBack={true}
|
|
243
|
+
snapBackDelay={1000}
|
|
244
|
+
/>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**How it works:**
|
|
248
|
+
|
|
249
|
+
- Pinch with two fingers to zoom in/out (min: 1x, max: configurable)
|
|
250
|
+
- Zoom centers on the focal point between your fingers
|
|
251
|
+
- Scroll position adjusts automatically to keep content in view
|
|
252
|
+
- Optional snap-back returns to initial zoom after a delay
|
|
253
|
+
- Smooth animations for zoom transitions
|
|
254
|
+
|
|
150
255
|
### Custom Time Range
|
|
151
256
|
|
|
152
257
|
```tsx
|
|
@@ -295,12 +400,6 @@ interface BookingData {
|
|
|
295
400
|
|
|
296
401
|
MIT
|
|
297
402
|
|
|
298
|
-
- The tab bar remains visible while navigating deeper into Explore-related screens
|
|
299
|
-
- Navigation context is preserved
|
|
300
|
-
- Platform-native gestures and transitions are maintained
|
|
301
|
-
|
|
302
|
-
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.
|
|
303
|
-
|
|
304
403
|
---
|
|
305
404
|
|
|
306
405
|
**Author:** Abdelrahman Sobhy
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { CalendarProps } from "../../types/calendar";
|
|
2
|
-
export declare function Calendar
|
|
2
|
+
export declare function Calendar({ date, resources, events, unavailableSlots, timeConfig, dimensions, zoomEnabled, maxZoom, initialZoom, snapBack, snapBackDelay, styles, eventStyles, unavailableStyles, onSlotPress, onEventPress, onEventDrop, onResourcePress, onDateChange, minDate, maxDate, allowPastDates, renderEvent, renderResourceHeader, renderTimeSlot, renderUnavailableSlot, showHeader, showTimeColumn, showResourceHeaders, enableHorizontalScroll, enableVerticalScroll, showDateNavigation, enableDragAndDrop, dateFormat, accessibilityLabel, }: CalendarProps): import("react").JSX.Element;
|
|
3
3
|
//# sourceMappingURL=Calendar.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Calendar.d.ts","sourceRoot":"","sources":["../../../components/calendar/Calendar.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Calendar.d.ts","sourceRoot":"","sources":["../../../components/calendar/Calendar.tsx"],"names":[],"mappings":"AAaA,OAAO,EAEL,aAAa,EAEd,MAAM,sBAAsB,CAAC;AAoB9B,wBAAgB,QAAQ,CAAC,EACvB,IAAI,EACJ,SAAS,EACT,MAAM,EACN,gBAAqB,EACrB,UAAU,EACV,UAAU,EACV,WAAmB,EACnB,OAAW,EACX,WAAe,EACf,QAAgB,EAChB,aAAoB,EACpB,MAAM,EACN,WAAW,EACX,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,WAAW,EACX,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,iBAAyB,EACzB,UAA+B,EAC/B,kBAA+B,GAChC,EAAE,aAAa,+BA8Pf"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { useMemo, useRef } from "react";
|
|
2
|
-
import {
|
|
1
|
+
import { useMemo, useRef, useState, useEffect } from "react";
|
|
2
|
+
import { View, } from "react-native";
|
|
3
|
+
import { Gesture, GestureDetector, ScrollView } from "react-native-gesture-handler";
|
|
4
|
+
import Animated, { useSharedValue, useAnimatedReaction, runOnJS, withTiming, } from "react-native-reanimated";
|
|
3
5
|
import { CalendarHeader } from "./CalendarHeader";
|
|
4
6
|
import { EventsLayer } from "./EventsLayer";
|
|
5
7
|
import { GridBody } from "./GridBody";
|
|
@@ -16,22 +18,111 @@ const DEFAULT_TIME_CONFIG = {
|
|
|
16
18
|
endHour: 24,
|
|
17
19
|
timeFormat: "HH:mm",
|
|
18
20
|
};
|
|
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", }) {
|
|
21
|
+
export function Calendar({ date, resources, events, unavailableSlots = [], timeConfig, dimensions, zoomEnabled = false, maxZoom = 3, initialZoom = 1, snapBack = false, snapBackDelay = 1000, styles, eventStyles, unavailableStyles, onSlotPress, onEventPress, onEventDrop, onResourcePress, onDateChange, minDate, maxDate, allowPastDates = false, renderEvent, renderResourceHeader, renderTimeSlot, renderUnavailableSlot, showHeader = true, showTimeColumn = true, showResourceHeaders = true, enableHorizontalScroll = true, enableVerticalScroll = true, showDateNavigation = true, enableDragAndDrop = false, dateFormat = "EEE, MMM d, yyyy", accessibilityLabel = "Calendar", }) {
|
|
20
22
|
const headerScrollRef = useRef(null);
|
|
21
23
|
const timeColumnScrollRef = useRef(null);
|
|
24
|
+
const horizontalScrollRef = useRef(null);
|
|
25
|
+
const verticalScrollRef = useRef(null);
|
|
26
|
+
// Zoom state
|
|
27
|
+
const minZoom = 1;
|
|
28
|
+
const zoomScale = useSharedValue(initialZoom);
|
|
29
|
+
const savedZoomScale = useSharedValue(initialZoom);
|
|
30
|
+
const [currentZoom, setCurrentZoom] = useState(initialZoom);
|
|
31
|
+
const [isPinching, setIsPinching] = useState(false);
|
|
32
|
+
const snapBackTimerRef = useRef(null);
|
|
33
|
+
const focalX = useRef(0);
|
|
34
|
+
const focalY = useRef(0);
|
|
35
|
+
const lastScrollX = useRef(0);
|
|
36
|
+
const lastScrollY = useRef(0);
|
|
37
|
+
// Update state when zoom changes
|
|
38
|
+
useAnimatedReaction(() => zoomScale.value, (zoom) => {
|
|
39
|
+
runOnJS(setCurrentZoom)(zoom);
|
|
40
|
+
});
|
|
41
|
+
// Adjust scroll position for focal point zooming
|
|
42
|
+
const prevZoom = useRef(initialZoom);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (currentZoom !== prevZoom.current && horizontalScrollRef.current && verticalScrollRef.current) {
|
|
45
|
+
const zoomRatio = currentZoom / prevZoom.current;
|
|
46
|
+
// Calculate new scroll position to keep focal point in place
|
|
47
|
+
horizontalScrollRef.current.scrollTo({
|
|
48
|
+
x: (lastScrollX.current + focalX.current) * zoomRatio - focalX.current,
|
|
49
|
+
animated: false,
|
|
50
|
+
});
|
|
51
|
+
verticalScrollRef.current.scrollTo({
|
|
52
|
+
y: (lastScrollY.current + focalY.current) * zoomRatio - focalY.current,
|
|
53
|
+
animated: false,
|
|
54
|
+
});
|
|
55
|
+
lastScrollX.current = (lastScrollX.current + focalX.current) * zoomRatio - focalX.current;
|
|
56
|
+
lastScrollY.current = (lastScrollY.current + focalY.current) * zoomRatio - focalY.current;
|
|
57
|
+
prevZoom.current = currentZoom;
|
|
58
|
+
}
|
|
59
|
+
}, [currentZoom]);
|
|
22
60
|
// Merge configurations with defaults
|
|
23
|
-
const
|
|
61
|
+
const baseDimensions = useMemo(() => ({ ...DEFAULT_DIMENSIONS, ...dimensions }), [dimensions]);
|
|
24
62
|
const finalTimeConfig = useMemo(() => ({ ...DEFAULT_TIME_CONFIG, ...timeConfig }), [timeConfig]);
|
|
63
|
+
// Apply zoom to dimensions - recalculate on zoom change
|
|
64
|
+
const finalDimensions = useMemo(() => ({
|
|
65
|
+
resourceWidth: baseDimensions.resourceWidth * currentZoom,
|
|
66
|
+
hourHeight: baseDimensions.hourHeight * currentZoom,
|
|
67
|
+
}), [baseDimensions, currentZoom]);
|
|
25
68
|
// Calculate grid dimensions
|
|
26
69
|
const gridTotalWidth = resources.length * finalDimensions.resourceWidth;
|
|
27
70
|
const totalHours = finalTimeConfig.endHour - finalTimeConfig.startHour;
|
|
28
71
|
const gridTotalHeight = totalHours * finalDimensions.hourHeight;
|
|
72
|
+
// Callbacks to update focal point (avoid accessing refs in worklets)
|
|
73
|
+
const updateFocalPoint = (x, y) => {
|
|
74
|
+
focalX.current = x;
|
|
75
|
+
focalY.current = y;
|
|
76
|
+
};
|
|
77
|
+
// Pinch gesture for zoom with focal point
|
|
78
|
+
const pinchGesture = Gesture.Pinch()
|
|
79
|
+
.enabled(zoomEnabled)
|
|
80
|
+
.onBegin((e) => {
|
|
81
|
+
'worklet';
|
|
82
|
+
// Clear any pending snap-back timer
|
|
83
|
+
if (snapBackTimerRef.current) {
|
|
84
|
+
clearTimeout(snapBackTimerRef.current);
|
|
85
|
+
snapBackTimerRef.current = null;
|
|
86
|
+
}
|
|
87
|
+
runOnJS(setIsPinching)(true);
|
|
88
|
+
runOnJS(updateFocalPoint)(e.focalX, e.focalY);
|
|
89
|
+
})
|
|
90
|
+
.onUpdate((e) => {
|
|
91
|
+
'worklet';
|
|
92
|
+
const newScale = savedZoomScale.value * e.scale;
|
|
93
|
+
const clampedScale = Math.max(minZoom, Math.min(maxZoom, newScale));
|
|
94
|
+
// Update zoom
|
|
95
|
+
zoomScale.value = clampedScale;
|
|
96
|
+
// Store focal point
|
|
97
|
+
runOnJS(updateFocalPoint)(e.focalX, e.focalY);
|
|
98
|
+
})
|
|
99
|
+
.onEnd(() => {
|
|
100
|
+
'worklet';
|
|
101
|
+
savedZoomScale.value = zoomScale.value;
|
|
102
|
+
runOnJS(setIsPinching)(false);
|
|
103
|
+
// Snap back with smooth animation
|
|
104
|
+
if (snapBack && zoomScale.value !== initialZoom) {
|
|
105
|
+
snapBackTimerRef.current = setTimeout(() => {
|
|
106
|
+
zoomScale.value = withTiming(initialZoom, {
|
|
107
|
+
duration: 400,
|
|
108
|
+
// Use easeOut for smoother deceleration
|
|
109
|
+
});
|
|
110
|
+
savedZoomScale.value = initialZoom;
|
|
111
|
+
}, snapBackDelay);
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
.onFinalize(() => {
|
|
115
|
+
'worklet';
|
|
116
|
+
runOnJS(setIsPinching)(false);
|
|
117
|
+
});
|
|
29
118
|
const handleHorizontalScroll = (scrollEvent) => {
|
|
30
119
|
const horizontalOffset = scrollEvent.nativeEvent.contentOffset.x;
|
|
120
|
+
lastScrollX.current = horizontalOffset;
|
|
31
121
|
headerScrollRef.current?.scrollTo({ x: horizontalOffset, animated: false });
|
|
32
122
|
};
|
|
33
123
|
const handleVerticalScroll = (scrollEvent) => {
|
|
34
124
|
const verticalOffset = scrollEvent.nativeEvent.contentOffset.y;
|
|
125
|
+
lastScrollY.current = verticalOffset;
|
|
35
126
|
timeColumnScrollRef.current?.scrollTo({
|
|
36
127
|
y: verticalOffset,
|
|
37
128
|
animated: false,
|
|
@@ -47,22 +138,26 @@ export function Calendar({ date, resources, events, unavailableSlots = [], timeC
|
|
|
47
138
|
{showTimeColumn && (<TimeColumn scrollRef={timeColumnScrollRef} gridTotalHeight={gridTotalHeight} timeConfig={finalTimeConfig} dimensions={finalDimensions} renderTimeSlot={renderTimeSlot} styles={styles}/>)}
|
|
48
139
|
|
|
49
140
|
<View style={{ flex: 1 }}>
|
|
50
|
-
<
|
|
51
|
-
<
|
|
141
|
+
<GestureDetector gesture={pinchGesture}>
|
|
142
|
+
<Animated.View style={{ flex: 1 }}>
|
|
143
|
+
<ScrollView ref={horizontalScrollRef} horizontal={enableHorizontalScroll} showsHorizontalScrollIndicator={enableHorizontalScroll} bounces={false} onScroll={handleHorizontalScroll} scrollEventThrottle={16} nestedScrollEnabled={true} scrollEnabled={enableHorizontalScroll && !isPinching}>
|
|
144
|
+
<ScrollView ref={verticalScrollRef} showsVerticalScrollIndicator={enableVerticalScroll} bounces={false} onScroll={handleVerticalScroll} scrollEventThrottle={16} nestedScrollEnabled={true} scrollEnabled={enableVerticalScroll && !isPinching} contentContainerStyle={{
|
|
52
145
|
width: gridTotalWidth,
|
|
53
146
|
height: gridTotalHeight,
|
|
54
147
|
}}>
|
|
55
|
-
|
|
148
|
+
<View style={{
|
|
56
149
|
position: "relative",
|
|
57
150
|
width: gridTotalWidth,
|
|
58
151
|
height: gridTotalHeight,
|
|
59
152
|
}}>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
153
|
+
<GridBody resources={resources} events={events} unavailableSlots={unavailableSlots} onSlotPress={onSlotPress} date={date} timeConfig={finalTimeConfig} dimensions={finalDimensions} styles={styles}/>
|
|
154
|
+
<UnavailableLayer unavailableSlots={unavailableSlots} resources={resources} timeConfig={finalTimeConfig} dimensions={finalDimensions} unavailableStyles={unavailableStyles} renderUnavailableSlot={renderUnavailableSlot}/>
|
|
155
|
+
<EventsLayer events={events} resources={resources} onEventPress={onEventPress} onEventDrop={onEventDrop} timeConfig={finalTimeConfig} dimensions={finalDimensions} eventStyles={eventStyles} renderEvent={renderEvent} enableDragAndDrop={enableDragAndDrop}/>
|
|
156
|
+
</View>
|
|
157
|
+
</ScrollView>
|
|
158
|
+
</ScrollView>
|
|
159
|
+
</Animated.View>
|
|
160
|
+
</GestureDetector>
|
|
66
161
|
</View>
|
|
67
162
|
</View>
|
|
68
163
|
</View>);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CalendarHeader.d.ts","sourceRoot":"","sources":["../../../components/calendar/CalendarHeader.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"CalendarHeader.d.ts","sourceRoot":"","sources":["../../../components/calendar/CalendarHeader.tsx"],"names":[],"mappings":"AAEA,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,+BAoIrB"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { MaterialIcons } from "@expo/vector-icons";
|
|
2
1
|
import { addDays, format, subDays } from "date-fns";
|
|
3
2
|
import { Pressable, Text, View } from "react-native";
|
|
4
3
|
export function CalendarHeader({ date, timeConfig, onDateChange, showDateNavigation = true, dateFormat = "EEE, MMM d, yyyy", minDate, maxDate, allowPastDates = true, }) {
|
|
@@ -90,7 +89,7 @@ export function CalendarHeader({ date, timeConfig, onDateChange, showDateNavigat
|
|
|
90
89
|
justifyContent: "center",
|
|
91
90
|
backgroundColor: "rgba(38, 38, 38, 0.75)",
|
|
92
91
|
}}>
|
|
93
|
-
<
|
|
92
|
+
<Text style={{ color: prevDisabled ? "#626262" : "#fefefe", fontSize: 24, fontWeight: "bold" }}>‹</Text>
|
|
94
93
|
</Pressable>
|
|
95
94
|
<Text style={{ color: "#fefefe", fontSize: 16, fontWeight: "600" }}>
|
|
96
95
|
{format(date, dateFormat)}
|
|
@@ -103,7 +102,7 @@ export function CalendarHeader({ date, timeConfig, onDateChange, showDateNavigat
|
|
|
103
102
|
alignItems: "center",
|
|
104
103
|
justifyContent: "center",
|
|
105
104
|
}}>
|
|
106
|
-
<
|
|
105
|
+
<Text style={{ color: nextDisabled ? "#626262" : "#fefefe", fontSize: 24, fontWeight: "bold" }}>›</Text>
|
|
107
106
|
</Pressable>
|
|
108
107
|
</>) : (<Text style={{ color: '#fefefe', fontSize: 16, fontWeight: '600' }}>
|
|
109
108
|
{format(date, dateFormat)}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { CalendarDimensions, CalendarEvent, CalendarEventStyles, CalendarTimeConfig, RenderEventFunction, Resource } from "../../types/calendar";
|
|
2
|
-
type EventsLayerProps
|
|
3
|
-
events: CalendarEvent
|
|
4
|
-
resources: Resource
|
|
5
|
-
onEventPress?: (event: CalendarEvent
|
|
1
|
+
import { CalendarDimensions, CalendarEvent, CalendarEventStyles, CalendarTimeConfig, DroppedEventData, RenderEventFunction, Resource } from "../../types/calendar";
|
|
2
|
+
type EventsLayerProps = {
|
|
3
|
+
events: CalendarEvent[];
|
|
4
|
+
resources: Resource[];
|
|
5
|
+
onEventPress?: (event: CalendarEvent) => void;
|
|
6
|
+
onEventDrop?: (droppedData: DroppedEventData, originalEvent: CalendarEvent) => void;
|
|
6
7
|
timeConfig: CalendarTimeConfig;
|
|
7
8
|
dimensions: CalendarDimensions;
|
|
8
9
|
eventStyles?: CalendarEventStyles;
|
|
9
|
-
renderEvent?: RenderEventFunction
|
|
10
|
+
renderEvent?: RenderEventFunction;
|
|
11
|
+
enableDragAndDrop?: boolean;
|
|
10
12
|
};
|
|
11
|
-
export declare function EventsLayer
|
|
13
|
+
export declare function EventsLayer({ events, resources, onEventPress, onEventDrop, timeConfig, dimensions, eventStyles, renderEvent, enableDragAndDrop, }: EventsLayerProps): import("react").JSX.Element;
|
|
12
14
|
export {};
|
|
13
15
|
//# sourceMappingURL=EventsLayer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventsLayer.d.ts","sourceRoot":"","sources":["../../../components/calendar/EventsLayer.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"EventsLayer.d.ts","sourceRoot":"","sources":["../../../components/calendar/EventsLayer.tsx"],"names":[],"mappings":"AASA,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAE9B,KAAK,gBAAgB,GAAG;IACtB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC9C,WAAW,CAAC,EAAE,CACZ,WAAW,EAAE,gBAAgB,EAC7B,aAAa,EAAE,aAAa,KACzB,IAAI,CAAC;IACV,UAAU,EAAE,kBAAkB,CAAC;IAC/B,UAAU,EAAE,kBAAkB,CAAC;IAC/B,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,SAAS,EACT,YAAY,EACZ,WAAW,EACX,UAAU,EACV,UAAU,EACV,WAAW,EACX,WAAW,EACX,iBAAyB,GAC1B,EAAE,gBAAgB,+BA6RlB"}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { useMemo } from "react";
|
|
1
|
+
import { useMemo, useState, useEffect } from "react";
|
|
2
2
|
import { Pressable, Text, View } from "react-native";
|
|
3
|
-
|
|
3
|
+
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
|
4
|
+
import Animated, { useAnimatedStyle, useSharedValue, withSpring, runOnJS, } from "react-native-reanimated";
|
|
5
|
+
export function EventsLayer({ events, resources, onEventPress, onEventDrop, timeConfig, dimensions, eventStyles, renderEvent, enableDragAndDrop = false, }) {
|
|
6
|
+
const [draggedEventId, setDraggedEventId] = useState(null);
|
|
7
|
+
const [dropPreview, setDropPreview] = useState(null);
|
|
4
8
|
const positionedEvents = useMemo(() => {
|
|
5
9
|
return events
|
|
6
10
|
.map((event) => {
|
|
@@ -25,7 +29,97 @@ export function EventsLayer({ events, resources, onEventPress, timeConfig, dimen
|
|
|
25
29
|
const gridTotalWidth = resources.length * dimensions.resourceWidth;
|
|
26
30
|
const gridBodyHeight = (timeConfig.endHour - timeConfig.startHour - 1) * dimensions.hourHeight;
|
|
27
31
|
const handleEventPress = (event) => {
|
|
28
|
-
|
|
32
|
+
if (!draggedEventId) {
|
|
33
|
+
onEventPress?.(event);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const updateDropPreview = (event, currentX, currentY, blockWidth, blockHeight, eventColor) => {
|
|
37
|
+
"worklet";
|
|
38
|
+
// Calculate snapped position
|
|
39
|
+
const newResourceIndex = Math.max(0, Math.min(Math.floor(currentX / dimensions.resourceWidth), resources.length - 1));
|
|
40
|
+
const hourOffset = currentY / dimensions.hourHeight;
|
|
41
|
+
const eventDuration = event.endHour - event.startHour;
|
|
42
|
+
let newStartHour = timeConfig.startHour + hourOffset;
|
|
43
|
+
// Snap to whole hour cells
|
|
44
|
+
newStartHour = Math.round(newStartHour);
|
|
45
|
+
// Clamp to valid range
|
|
46
|
+
newStartHour = Math.max(timeConfig.startHour, Math.min(timeConfig.endHour - eventDuration, newStartHour));
|
|
47
|
+
const snappedTop = (newStartHour - timeConfig.startHour - 1) * dimensions.hourHeight;
|
|
48
|
+
const snappedLeft = newResourceIndex * dimensions.resourceWidth;
|
|
49
|
+
runOnJS(setDropPreview)({
|
|
50
|
+
top: snappedTop,
|
|
51
|
+
left: snappedLeft,
|
|
52
|
+
width: blockWidth,
|
|
53
|
+
height: blockHeight,
|
|
54
|
+
color: eventColor,
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
const handleEventDrop = (event, finalX, finalY) => {
|
|
58
|
+
"worklet";
|
|
59
|
+
// Calculate new resource and time
|
|
60
|
+
const newResourceIndex = Math.max(0, Math.min(Math.floor(finalX / dimensions.resourceWidth), resources.length - 1));
|
|
61
|
+
const newResource = resources[newResourceIndex];
|
|
62
|
+
const hourOffset = finalY / dimensions.hourHeight;
|
|
63
|
+
const eventDuration = event.endHour - event.startHour;
|
|
64
|
+
let newStartHour = timeConfig.startHour + hourOffset;
|
|
65
|
+
// Snap to whole hour cells
|
|
66
|
+
newStartHour = Math.round(newStartHour);
|
|
67
|
+
// Clamp to valid range
|
|
68
|
+
newStartHour = Math.max(timeConfig.startHour, Math.min(timeConfig.endHour - eventDuration, newStartHour));
|
|
69
|
+
const newEndHour = newStartHour + eventDuration;
|
|
70
|
+
// Only trigger callback if position changed significantly
|
|
71
|
+
if (newResource.id !== event.resourceId ||
|
|
72
|
+
Math.abs(newStartHour - event.startHour) > 0.1) {
|
|
73
|
+
runOnJS(onEventDrop)({
|
|
74
|
+
eventId: event.id,
|
|
75
|
+
newResourceId: newResource.id,
|
|
76
|
+
newStartHour,
|
|
77
|
+
newEndHour,
|
|
78
|
+
}, event);
|
|
79
|
+
}
|
|
80
|
+
runOnJS(setDraggedEventId)(null);
|
|
81
|
+
runOnJS(setDropPreview)(null);
|
|
82
|
+
};
|
|
83
|
+
const createGesture = (event, layoutX, layoutY, translateX, translateY, blockWidth, blockHeight, eventColor) => {
|
|
84
|
+
const longPress = Gesture.LongPress()
|
|
85
|
+
.minDuration(500)
|
|
86
|
+
.onStart(() => {
|
|
87
|
+
if (enableDragAndDrop) {
|
|
88
|
+
runOnJS(setDraggedEventId)(event.id);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
const pan = Gesture.Pan()
|
|
92
|
+
.enabled(enableDragAndDrop)
|
|
93
|
+
.activeOffsetX([-5, 5])
|
|
94
|
+
.activeOffsetY([-5, 5])
|
|
95
|
+
.onUpdate((e) => {
|
|
96
|
+
translateX.value = e.translationX;
|
|
97
|
+
translateY.value = e.translationY;
|
|
98
|
+
// Update preview position
|
|
99
|
+
const currentX = layoutX + e.translationX;
|
|
100
|
+
const currentY = layoutY + e.translationY;
|
|
101
|
+
updateDropPreview(event, currentX, currentY, blockWidth, blockHeight, eventColor);
|
|
102
|
+
})
|
|
103
|
+
.onEnd(() => {
|
|
104
|
+
const finalX = layoutX + translateX.value;
|
|
105
|
+
const finalY = layoutY + translateY.value;
|
|
106
|
+
// Calculate snapped position
|
|
107
|
+
const newResourceIndex = Math.max(0, Math.min(Math.floor(finalX / dimensions.resourceWidth), resources.length - 1));
|
|
108
|
+
const hourOffset = finalY / dimensions.hourHeight;
|
|
109
|
+
const eventDuration = event.endHour - event.startHour;
|
|
110
|
+
let newStartHour = timeConfig.startHour + hourOffset;
|
|
111
|
+
newStartHour = Math.round(newStartHour);
|
|
112
|
+
newStartHour = Math.max(timeConfig.startHour, Math.min(timeConfig.endHour - eventDuration, newStartHour));
|
|
113
|
+
const snappedTop = (newStartHour - timeConfig.startHour - 1) * dimensions.hourHeight;
|
|
114
|
+
const snappedLeft = newResourceIndex * dimensions.resourceWidth;
|
|
115
|
+
// Snap instantly to final position and KEEP it there
|
|
116
|
+
translateX.value = snappedLeft - layoutX;
|
|
117
|
+
translateY.value = snappedTop - layoutY;
|
|
118
|
+
// Trigger the drop callback - React will re-render with new position
|
|
119
|
+
handleEventDrop(event, finalX, finalY);
|
|
120
|
+
// Don't reset - let React take over when it re-renders with updated event data
|
|
121
|
+
});
|
|
122
|
+
return Gesture.Simultaneous(longPress, pan);
|
|
29
123
|
};
|
|
30
124
|
const defaultColor = eventStyles?.defaultColor || "#3b82f6";
|
|
31
125
|
const borderRadius = eventStyles?.borderRadius || 8;
|
|
@@ -38,42 +132,96 @@ export function EventsLayer({ events, resources, onEventPress, timeConfig, dimen
|
|
|
38
132
|
width: gridTotalWidth,
|
|
39
133
|
height: gridBodyHeight,
|
|
40
134
|
}} pointerEvents="box-none">
|
|
135
|
+
{/* Drop preview ghost */}
|
|
136
|
+
{dropPreview && (<View style={{
|
|
137
|
+
position: "absolute",
|
|
138
|
+
top: dropPreview.top,
|
|
139
|
+
left: dropPreview.left,
|
|
140
|
+
width: dropPreview.width,
|
|
141
|
+
height: dropPreview.height,
|
|
142
|
+
backgroundColor: dropPreview.color,
|
|
143
|
+
opacity: 0.3,
|
|
144
|
+
borderRadius,
|
|
145
|
+
borderWidth: 2,
|
|
146
|
+
borderColor: "#ffffff",
|
|
147
|
+
borderStyle: "dashed",
|
|
148
|
+
zIndex: 5,
|
|
149
|
+
}}/>)}
|
|
150
|
+
|
|
41
151
|
{positionedEvents.map((item) => {
|
|
42
152
|
const eventColor = item.event.color || defaultColor;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
justifyContent: "center",
|
|
46
|
-
alignItems: "center",
|
|
47
|
-
backgroundColor: "#1a1a1a",
|
|
48
|
-
top: item.topPosition,
|
|
49
|
-
left: item.leftPosition,
|
|
50
|
-
height: item.blockHeight,
|
|
51
|
-
width: item.blockWidth,
|
|
52
|
-
}}>
|
|
53
|
-
<Pressable onPress={() => handleEventPress(item.event)} style={{
|
|
54
|
-
height: item.blockHeight - 2,
|
|
55
|
-
width: item.blockWidth - 2,
|
|
56
|
-
backgroundColor: eventColor,
|
|
57
|
-
borderRadius,
|
|
58
|
-
opacity,
|
|
59
|
-
justifyContent: "center",
|
|
60
|
-
alignItems: "center",
|
|
61
|
-
padding: 8,
|
|
62
|
-
overflow: "hidden",
|
|
63
|
-
}}>
|
|
64
|
-
{renderEvent ? (renderEvent(item.event, {
|
|
65
|
-
width: item.blockWidth - 2,
|
|
66
|
-
height: item.blockHeight - 2,
|
|
67
|
-
})) : (<Text style={{
|
|
68
|
-
color: "#ffffff",
|
|
69
|
-
fontSize: 14,
|
|
70
|
-
fontWeight: "600",
|
|
71
|
-
textAlign: "center",
|
|
72
|
-
}} numberOfLines={2}>
|
|
73
|
-
{item.event.title}
|
|
74
|
-
</Text>)}
|
|
75
|
-
</Pressable>
|
|
76
|
-
</View>);
|
|
153
|
+
const isDragging = draggedEventId === item.event.id;
|
|
154
|
+
return (<EventBlock key={item.event.id} event={item.event} topPosition={item.topPosition} leftPosition={item.leftPosition} blockHeight={item.blockHeight} blockWidth={item.blockWidth} eventColor={eventColor} isDragging={isDragging} borderRadius={borderRadius} opacity={opacity} enableDragAndDrop={enableDragAndDrop} onPress={handleEventPress} createGesture={createGesture} renderEvent={renderEvent} updateDropPreview={updateDropPreview}/>);
|
|
77
155
|
})}
|
|
78
156
|
</View>);
|
|
79
157
|
}
|
|
158
|
+
function EventBlock({ event, topPosition, leftPosition, blockHeight, blockWidth, eventColor, isDragging, borderRadius, opacity, enableDragAndDrop, onPress, createGesture, renderEvent, updateDropPreview, }) {
|
|
159
|
+
const translateX = useSharedValue(0);
|
|
160
|
+
const translateY = useSharedValue(0);
|
|
161
|
+
const scale = useSharedValue(1);
|
|
162
|
+
// Reset translation when position changes (event data updated)
|
|
163
|
+
// Use the event's position as a key to detect changes
|
|
164
|
+
const positionKey = `${topPosition}-${leftPosition}`;
|
|
165
|
+
const prevPositionKey = useSharedValue(positionKey);
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (prevPositionKey.value !== positionKey && !isDragging) {
|
|
168
|
+
translateX.value = 0;
|
|
169
|
+
translateY.value = 0;
|
|
170
|
+
prevPositionKey.value = positionKey;
|
|
171
|
+
}
|
|
172
|
+
}, [positionKey, isDragging, prevPositionKey, translateX, translateY]);
|
|
173
|
+
const gesture = enableDragAndDrop
|
|
174
|
+
? createGesture(event, leftPosition, topPosition, translateX, translateY, blockWidth, blockHeight, eventColor)
|
|
175
|
+
: undefined;
|
|
176
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
177
|
+
transform: [
|
|
178
|
+
{ translateX: translateX.value },
|
|
179
|
+
{ translateY: translateY.value },
|
|
180
|
+
{ scale: isDragging ? withSpring(1.05) : withSpring(1) },
|
|
181
|
+
],
|
|
182
|
+
}));
|
|
183
|
+
return (<Animated.View style={[
|
|
184
|
+
{
|
|
185
|
+
position: "absolute",
|
|
186
|
+
justifyContent: "center",
|
|
187
|
+
alignItems: "center",
|
|
188
|
+
backgroundColor: "#1a1a1a",
|
|
189
|
+
top: topPosition,
|
|
190
|
+
left: leftPosition,
|
|
191
|
+
height: blockHeight,
|
|
192
|
+
width: blockWidth,
|
|
193
|
+
zIndex: isDragging ? 1000 : 10,
|
|
194
|
+
},
|
|
195
|
+
animatedStyle,
|
|
196
|
+
]}>
|
|
197
|
+
<GestureDetector gesture={gesture || Gesture.Tap()}>
|
|
198
|
+
<Animated.View style={{ width: "100%", height: "100%" }}>
|
|
199
|
+
<Pressable onPress={() => onPress(event)} style={{
|
|
200
|
+
height: blockHeight - 2,
|
|
201
|
+
width: blockWidth - 2,
|
|
202
|
+
backgroundColor: eventColor,
|
|
203
|
+
borderRadius,
|
|
204
|
+
opacity: isDragging ? 0.7 : opacity,
|
|
205
|
+
justifyContent: "center",
|
|
206
|
+
alignItems: "center",
|
|
207
|
+
padding: 8,
|
|
208
|
+
overflow: "hidden",
|
|
209
|
+
borderWidth: isDragging ? 2 : 0,
|
|
210
|
+
borderColor: "#ffffff",
|
|
211
|
+
}}>
|
|
212
|
+
{renderEvent ? (renderEvent(event, {
|
|
213
|
+
width: blockWidth - 2,
|
|
214
|
+
height: blockHeight - 2,
|
|
215
|
+
})) : (<Text style={{
|
|
216
|
+
color: "#ffffff",
|
|
217
|
+
fontSize: 14,
|
|
218
|
+
fontWeight: "600",
|
|
219
|
+
textAlign: "center",
|
|
220
|
+
}} numberOfLines={2}>
|
|
221
|
+
{event.title}
|
|
222
|
+
</Text>)}
|
|
223
|
+
</Pressable>
|
|
224
|
+
</Animated.View>
|
|
225
|
+
</GestureDetector>
|
|
226
|
+
</Animated.View>);
|
|
227
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { CalendarDimensions, CalendarEvent, CalendarStyles, CalendarTimeConfig, Resource, UnavailableSlot } from "../../types/calendar";
|
|
2
|
-
type GridBodyProps
|
|
3
|
-
resources: Resource
|
|
4
|
-
events: CalendarEvent
|
|
5
|
-
unavailableSlots: UnavailableSlot
|
|
2
|
+
type GridBodyProps = {
|
|
3
|
+
resources: Resource[];
|
|
4
|
+
events: CalendarEvent[];
|
|
5
|
+
unavailableSlots: UnavailableSlot[];
|
|
6
6
|
onSlotPress?: (hour: number, resourceId: string, date: Date) => void;
|
|
7
7
|
date: Date;
|
|
8
8
|
timeConfig: CalendarTimeConfig;
|
|
9
9
|
dimensions: CalendarDimensions;
|
|
10
10
|
styles?: CalendarStyles;
|
|
11
11
|
};
|
|
12
|
-
export declare function GridBody
|
|
12
|
+
export declare function GridBody({ resources, events, unavailableSlots, onSlotPress, date, timeConfig, dimensions, styles, }: GridBodyProps): import("react").JSX.Element;
|
|
13
13
|
export {};
|
|
14
14
|
//# sourceMappingURL=GridBody.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GridBody.d.ts","sourceRoot":"","sources":["../../../components/calendar/GridBody.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"GridBody.d.ts","sourceRoot":"","sources":["../../../components/calendar/GridBody.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,QAAQ,EACR,eAAe,EAChB,MAAM,sBAAsB,CAAC;AAE9B,KAAK,aAAa,GAAG;IACnB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACrE,IAAI,EAAE,IAAI,CAAC;IACX,UAAU,EAAE,kBAAkB,CAAC;IAC/B,UAAU,EAAE,kBAAkB,CAAC;IAC/B,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB,CAAC;AAEF,wBAAgB,QAAQ,CAAC,EACvB,SAAS,EACT,MAAM,EACN,gBAAgB,EAChB,WAAW,EACX,IAAI,EACJ,UAAU,EACV,UAAU,EACV,MAAM,GACP,EAAE,aAAa,+BAmGf"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { MaterialIcons } from "@expo/vector-icons";
|
|
2
1
|
import { useMemo } from "react";
|
|
3
|
-
import { Pressable, View } from "react-native";
|
|
2
|
+
import { Pressable, Text, View } from "react-native";
|
|
4
3
|
export function GridBody({ resources, events, unavailableSlots, onSlotPress, date, timeConfig, dimensions, styles, }) {
|
|
5
4
|
const hoursArray = useMemo(() => {
|
|
6
5
|
const result = [];
|
|
@@ -62,7 +61,9 @@ export function GridBody({ resources, events, unavailableSlots, onSlotPress, dat
|
|
|
62
61
|
justifyContent: "center",
|
|
63
62
|
alignItems: "center",
|
|
64
63
|
}}>
|
|
65
|
-
<
|
|
64
|
+
<Text style={{ color: "#474747", fontSize: 20, fontWeight: "300" }}>
|
|
65
|
+
+
|
|
66
|
+
</Text>
|
|
66
67
|
</Pressable>);
|
|
67
68
|
})}
|
|
68
69
|
</View>))}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { ScrollView } from "react-native";
|
|
3
3
|
import { CalendarDimensions, RenderResourceHeaderFunction, Resource } from "../../types/calendar";
|
|
4
|
-
type ResourceHeadersProps
|
|
5
|
-
resources: Resource
|
|
4
|
+
type ResourceHeadersProps = {
|
|
5
|
+
resources: Resource[];
|
|
6
6
|
scrollRef: React.RefObject<ScrollView | null>;
|
|
7
7
|
dimensions: CalendarDimensions;
|
|
8
|
-
renderResourceHeader?: RenderResourceHeaderFunction
|
|
9
|
-
onResourcePress?: (resource: Resource
|
|
8
|
+
renderResourceHeader?: RenderResourceHeaderFunction;
|
|
9
|
+
onResourcePress?: (resource: Resource) => void;
|
|
10
10
|
};
|
|
11
|
-
export declare function ResourceHeaders
|
|
11
|
+
export declare function ResourceHeaders({ resources, scrollRef, dimensions, renderResourceHeader, onResourcePress, }: ResourceHeadersProps): React.JSX.Element;
|
|
12
12
|
export {};
|
|
13
13
|
//# sourceMappingURL=ResourceHeaders.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResourceHeaders.d.ts","sourceRoot":"","sources":["../../../components/calendar/ResourceHeaders.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAa,UAAU,EAAc,MAAM,cAAc,CAAC;AACjE,OAAO,EACL,kBAAkB,EAClB,4BAA4B,EAC5B,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAE9B,KAAK,oBAAoB,
|
|
1
|
+
{"version":3,"file":"ResourceHeaders.d.ts","sourceRoot":"","sources":["../../../components/calendar/ResourceHeaders.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAa,UAAU,EAAc,MAAM,cAAc,CAAC;AACjE,OAAO,EACL,kBAAkB,EAClB,4BAA4B,EAC5B,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAE9B,KAAK,oBAAoB,GAAG;IAC1B,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAC9C,UAAU,EAAE,kBAAkB,CAAC;IAC/B,oBAAoB,CAAC,EAAE,4BAA4B,CAAC;IACpD,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;CAChD,CAAC;AAEF,wBAAgB,eAAe,CAAC,EAC9B,SAAS,EACT,SAAS,EACT,UAAU,EACV,oBAAoB,EACpB,eAAe,GAChB,EAAE,oBAAoB,qBAiDtB"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { CalendarDimensions, CalendarTimeConfig, RenderUnavailableSlotFunction, Resource, UnavailableSlot, UnavailableSlotStyles } from "../../types/calendar";
|
|
2
|
-
type UnavailableLayerProps
|
|
3
|
-
unavailableSlots: UnavailableSlot
|
|
4
|
-
resources: Resource
|
|
2
|
+
type UnavailableLayerProps = {
|
|
3
|
+
unavailableSlots: UnavailableSlot[];
|
|
4
|
+
resources: Resource[];
|
|
5
5
|
timeConfig: CalendarTimeConfig;
|
|
6
6
|
dimensions: CalendarDimensions;
|
|
7
7
|
unavailableStyles?: UnavailableSlotStyles;
|
|
8
|
-
renderUnavailableSlot?: RenderUnavailableSlotFunction
|
|
8
|
+
renderUnavailableSlot?: RenderUnavailableSlotFunction;
|
|
9
9
|
};
|
|
10
|
-
export declare function UnavailableLayer<TResource =
|
|
10
|
+
export declare function UnavailableLayer<TResource = unknown, TUnavailable = unknown>({ unavailableSlots, resources, timeConfig, dimensions, unavailableStyles, renderUnavailableSlot, }: UnavailableLayerProps): import("react").JSX.Element;
|
|
11
11
|
export {};
|
|
12
12
|
//# sourceMappingURL=UnavailableLayer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UnavailableLayer.d.ts","sourceRoot":"","sources":["../../../components/calendar/UnavailableLayer.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,6BAA6B,EAC7B,QAAQ,EACR,eAAe,EACf,qBAAqB,EACtB,MAAM,sBAAsB,CAAC;AAE9B,KAAK,qBAAqB,
|
|
1
|
+
{"version":3,"file":"UnavailableLayer.d.ts","sourceRoot":"","sources":["../../../components/calendar/UnavailableLayer.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,6BAA6B,EAC7B,QAAQ,EACR,eAAe,EACf,qBAAqB,EACtB,MAAM,sBAAsB,CAAC;AAE9B,KAAK,qBAAqB,GAAG;IAC3B,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,UAAU,EAAE,kBAAkB,CAAC;IAC/B,iBAAiB,CAAC,EAAE,qBAAqB,CAAC;IAC1C,qBAAqB,CAAC,EAAE,6BAA6B,CAAC;CACvD,CAAC;AAuDF,wBAAgB,gBAAgB,CAAC,SAAS,GAAG,OAAO,EAAE,YAAY,GAAG,OAAO,EAAE,EAC5E,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,UAAU,EACV,iBAAiB,EACjB,qBAAqB,GACtB,EAAE,qBAAqB,+BAkIvB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface ZoomControlsProps {
|
|
2
|
+
currentZoom: number;
|
|
3
|
+
minZoom: number;
|
|
4
|
+
maxZoom: number;
|
|
5
|
+
onZoomIn: () => void;
|
|
6
|
+
onZoomOut: () => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function ZoomControls({ currentZoom, minZoom, maxZoom, onZoomIn, onZoomOut, }: ZoomControlsProps): import("react").JSX.Element;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=ZoomControls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ZoomControls.d.ts","sourceRoot":"","sources":["../../../components/calendar/ZoomControls.tsx"],"names":[],"mappings":"AAEA,UAAU,iBAAiB;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,wBAAgB,YAAY,CAAC,EAC3B,WAAW,EACX,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,GACV,EAAE,iBAAiB,+BA6BnB"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
|
|
2
|
+
export function ZoomControls({ currentZoom, minZoom, maxZoom, onZoomIn, onZoomOut, }) {
|
|
3
|
+
const canZoomIn = currentZoom < maxZoom;
|
|
4
|
+
const canZoomOut = currentZoom > minZoom;
|
|
5
|
+
return (<View style={styles.container}>
|
|
6
|
+
<TouchableOpacity style={[styles.button, !canZoomOut && styles.buttonDisabled]} onPress={onZoomOut} disabled={!canZoomOut} accessibilityLabel="Zoom out" accessibilityRole="button">
|
|
7
|
+
<Text style={[styles.buttonText, !canZoomOut && styles.textDisabled]}>
|
|
8
|
+
−
|
|
9
|
+
</Text>
|
|
10
|
+
</TouchableOpacity>
|
|
11
|
+
<Text style={styles.zoomText}>{Math.round(currentZoom * 100)}%</Text>
|
|
12
|
+
<TouchableOpacity style={[styles.button, !canZoomIn && styles.buttonDisabled]} onPress={onZoomIn} disabled={!canZoomIn} accessibilityLabel="Zoom in" accessibilityRole="button">
|
|
13
|
+
<Text style={[styles.buttonText, !canZoomIn && styles.textDisabled]}>
|
|
14
|
+
+
|
|
15
|
+
</Text>
|
|
16
|
+
</TouchableOpacity>
|
|
17
|
+
</View>);
|
|
18
|
+
}
|
|
19
|
+
const styles = StyleSheet.create({
|
|
20
|
+
container: {
|
|
21
|
+
position: "absolute",
|
|
22
|
+
bottom: 20,
|
|
23
|
+
right: 20,
|
|
24
|
+
flexDirection: "row",
|
|
25
|
+
alignItems: "center",
|
|
26
|
+
backgroundColor: "#2a2a2a",
|
|
27
|
+
borderRadius: 8,
|
|
28
|
+
padding: 4,
|
|
29
|
+
elevation: 4,
|
|
30
|
+
shadowColor: "#000",
|
|
31
|
+
shadowOffset: { width: 0, height: 2 },
|
|
32
|
+
shadowOpacity: 0.3,
|
|
33
|
+
shadowRadius: 4,
|
|
34
|
+
zIndex: 1000,
|
|
35
|
+
},
|
|
36
|
+
button: {
|
|
37
|
+
width: 36,
|
|
38
|
+
height: 36,
|
|
39
|
+
justifyContent: "center",
|
|
40
|
+
alignItems: "center",
|
|
41
|
+
backgroundColor: "#3a3a3a",
|
|
42
|
+
borderRadius: 6,
|
|
43
|
+
},
|
|
44
|
+
buttonDisabled: {
|
|
45
|
+
opacity: 0.3,
|
|
46
|
+
},
|
|
47
|
+
buttonText: {
|
|
48
|
+
color: "#fff",
|
|
49
|
+
fontSize: 24,
|
|
50
|
+
fontWeight: "600",
|
|
51
|
+
},
|
|
52
|
+
textDisabled: {
|
|
53
|
+
color: "#666",
|
|
54
|
+
},
|
|
55
|
+
zoomText: {
|
|
56
|
+
color: "#fff",
|
|
57
|
+
fontSize: 14,
|
|
58
|
+
fontWeight: "500",
|
|
59
|
+
marginHorizontal: 12,
|
|
60
|
+
minWidth: 45,
|
|
61
|
+
textAlign: "center",
|
|
62
|
+
},
|
|
63
|
+
});
|
package/dist/types/calendar.d.ts
CHANGED
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
|
-
export type Resource
|
|
2
|
+
export type Resource = {
|
|
3
3
|
id: string;
|
|
4
4
|
label: string;
|
|
5
|
-
data?: T;
|
|
6
5
|
};
|
|
7
|
-
export type CalendarEvent
|
|
6
|
+
export type CalendarEvent = {
|
|
8
7
|
id: string;
|
|
9
8
|
title: string;
|
|
10
9
|
resourceId: string;
|
|
11
10
|
startHour: number;
|
|
12
11
|
endHour: number;
|
|
13
12
|
color?: string;
|
|
14
|
-
|
|
13
|
+
};
|
|
14
|
+
export type DroppedEventData = {
|
|
15
|
+
eventId: string;
|
|
16
|
+
newResourceId: string;
|
|
17
|
+
newStartHour: number;
|
|
18
|
+
newEndHour: number;
|
|
15
19
|
};
|
|
16
20
|
export type UnavailableType = "reserved" | "offHours" | "disabled";
|
|
17
|
-
export type UnavailableSlot
|
|
21
|
+
export type UnavailableSlot = {
|
|
18
22
|
id: string;
|
|
19
23
|
resourceId: string;
|
|
20
24
|
startHour: number;
|
|
21
25
|
endHour: number;
|
|
22
26
|
type: UnavailableType;
|
|
23
|
-
data?: T;
|
|
24
27
|
};
|
|
25
28
|
export type CalendarDimensions = {
|
|
26
29
|
/** Width of each resource column in pixels */
|
|
@@ -75,43 +78,50 @@ export type UnavailableSlotStyles = {
|
|
|
75
78
|
opacity?: number;
|
|
76
79
|
};
|
|
77
80
|
};
|
|
78
|
-
export type RenderEventFunction
|
|
81
|
+
export type RenderEventFunction = (event: CalendarEvent, dimensions: {
|
|
79
82
|
width: number;
|
|
80
83
|
height: number;
|
|
81
84
|
}) => ReactNode;
|
|
82
|
-
export type RenderResourceHeaderFunction
|
|
85
|
+
export type RenderResourceHeaderFunction = (resource: Resource) => ReactNode;
|
|
83
86
|
export type RenderTimeSlotFunction = (hour: number) => ReactNode;
|
|
84
|
-
export type RenderUnavailableSlotFunction
|
|
87
|
+
export type RenderUnavailableSlotFunction = (slot: UnavailableSlot, dimensions: {
|
|
85
88
|
width: number;
|
|
86
89
|
height: number;
|
|
87
90
|
}) => ReactNode;
|
|
88
|
-
export type CalendarProps
|
|
91
|
+
export type CalendarProps = {
|
|
89
92
|
date: Date;
|
|
90
|
-
resources: Resource
|
|
91
|
-
events: CalendarEvent
|
|
92
|
-
unavailableSlots?: UnavailableSlot
|
|
93
|
+
resources: Resource[];
|
|
94
|
+
events: CalendarEvent[];
|
|
95
|
+
unavailableSlots?: UnavailableSlot[];
|
|
93
96
|
timeConfig?: CalendarTimeConfig;
|
|
94
97
|
dimensions?: Partial<CalendarDimensions>;
|
|
95
98
|
styles?: CalendarStyles;
|
|
96
99
|
eventStyles?: CalendarEventStyles;
|
|
97
100
|
unavailableStyles?: UnavailableSlotStyles;
|
|
101
|
+
zoomEnabled?: boolean;
|
|
102
|
+
maxZoom?: number;
|
|
103
|
+
initialZoom?: number;
|
|
104
|
+
snapBack?: boolean;
|
|
105
|
+
snapBackDelay?: number;
|
|
98
106
|
onSlotPress?: (hour: number, resourceId: string, date: Date) => void;
|
|
99
|
-
onEventPress?: (event: CalendarEvent
|
|
100
|
-
onResourcePress?: (resource: Resource
|
|
107
|
+
onEventPress?: (event: CalendarEvent) => void;
|
|
108
|
+
onResourcePress?: (resource: Resource) => void;
|
|
101
109
|
onDateChange?: (date: Date) => void;
|
|
110
|
+
onEventDrop?: (droppedData: DroppedEventData, originalEvent: CalendarEvent) => void;
|
|
102
111
|
minDate?: Date;
|
|
103
112
|
maxDate?: Date;
|
|
104
113
|
allowPastDates?: boolean;
|
|
105
|
-
renderEvent?: RenderEventFunction
|
|
106
|
-
renderResourceHeader?: RenderResourceHeaderFunction
|
|
114
|
+
renderEvent?: RenderEventFunction;
|
|
115
|
+
renderResourceHeader?: RenderResourceHeaderFunction;
|
|
107
116
|
renderTimeSlot?: RenderTimeSlotFunction;
|
|
108
|
-
renderUnavailableSlot?: RenderUnavailableSlotFunction
|
|
117
|
+
renderUnavailableSlot?: RenderUnavailableSlotFunction;
|
|
109
118
|
showHeader?: boolean;
|
|
110
119
|
showTimeColumn?: boolean;
|
|
111
120
|
showResourceHeaders?: boolean;
|
|
112
121
|
enableHorizontalScroll?: boolean;
|
|
113
122
|
enableVerticalScroll?: boolean;
|
|
114
123
|
showDateNavigation?: boolean;
|
|
124
|
+
enableDragAndDrop?: boolean;
|
|
115
125
|
dateFormat?: string;
|
|
116
126
|
accessibilityLabel?: string;
|
|
117
127
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../types/calendar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGlC,MAAM,MAAM,QAAQ,
|
|
1
|
+
{"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../types/calendar.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-calendar-resource",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "A fully customizable, type-safe resource-based calendar component for React Native",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"build": "tsc",
|
|
8
|
+
"build": "tsc -p tsconfig.package.json",
|
|
9
|
+
"watch": "tsc -p tsconfig.package.json --watch",
|
|
9
10
|
"prepublishOnly": "npm run build",
|
|
10
11
|
"test": "echo \"No tests yet\" && exit 0"
|
|
11
12
|
},
|
|
@@ -24,15 +25,29 @@
|
|
|
24
25
|
"type": "git",
|
|
25
26
|
"url": "https://github.com/abdosobhy1/react-native-calendar-resource"
|
|
26
27
|
},
|
|
28
|
+
"homepage": "https://github.com/abdosobhy1/react-native-calendar-resource#readme",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/abdosobhy1/react-native-calendar-resource/issues"
|
|
31
|
+
},
|
|
27
32
|
"peerDependencies": {
|
|
28
33
|
"react": ">=18.0.0",
|
|
29
34
|
"react-native": ">=0.70.0",
|
|
30
|
-
"date-fns": ">=3.0.0"
|
|
35
|
+
"date-fns": ">=3.0.0",
|
|
36
|
+
"react-native-gesture-handler": ">=2.9.0",
|
|
37
|
+
"react-native-reanimated": ">=2.14.0"
|
|
31
38
|
},
|
|
32
39
|
"devDependencies": {
|
|
33
40
|
"@types/react": "^19.1.0",
|
|
34
41
|
"@types/react-native": "^0.73.0",
|
|
42
|
+
"react": "^18.2.0",
|
|
43
|
+
"react-native": "^0.73.0",
|
|
44
|
+
"react-native-gesture-handler": "^2.14.0",
|
|
45
|
+
"react-native-reanimated": "^3.6.0",
|
|
35
46
|
"typescript": "^5.9.2"
|
|
36
47
|
},
|
|
37
|
-
"files": [
|
|
48
|
+
"files": [
|
|
49
|
+
"dist",
|
|
50
|
+
"README.md",
|
|
51
|
+
"LICENSE"
|
|
52
|
+
]
|
|
38
53
|
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Native date utility functions to replace date-fns
|
|
3
|
-
* Zero external dependencies
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* Add days to a date
|
|
7
|
-
*/
|
|
8
|
-
export declare function addDays(date: Date, days: number): Date;
|
|
9
|
-
/**
|
|
10
|
-
* Subtract days from a date
|
|
11
|
-
*/
|
|
12
|
-
export declare function subDays(date: Date, days: number): Date;
|
|
13
|
-
/**
|
|
14
|
-
* Format a date according to a format string
|
|
15
|
-
* Supports common tokens:
|
|
16
|
-
* - EEE: Short weekday name (Mon, Tue, etc.)
|
|
17
|
-
* - EEEE: Full weekday name (Monday, Tuesday, etc.)
|
|
18
|
-
* - MMM: Short month name (Jan, Feb, etc.)
|
|
19
|
-
* - MMMM: Full month name (January, February, etc.)
|
|
20
|
-
* - MM: Month as 2 digits (01-12)
|
|
21
|
-
* - M: Month as 1-2 digits (1-12)
|
|
22
|
-
* - dd: Day as 2 digits (01-31)
|
|
23
|
-
* - d: Day as 1-2 digits (1-31)
|
|
24
|
-
* - yyyy: Full year (2026)
|
|
25
|
-
* - yy: 2-digit year (26)
|
|
26
|
-
* - HH: Hour in 24h format with leading zero (00-23)
|
|
27
|
-
* - H: Hour in 24h format (0-23)
|
|
28
|
-
* - hh: Hour in 12h format with leading zero (01-12)
|
|
29
|
-
* - h: Hour in 12h format (1-12)
|
|
30
|
-
* - mm: Minutes with leading zero (00-59)
|
|
31
|
-
* - m: Minutes (0-59)
|
|
32
|
-
* - ss: Seconds with leading zero (00-59)
|
|
33
|
-
* - s: Seconds (0-59)
|
|
34
|
-
* - a: AM/PM
|
|
35
|
-
*/
|
|
36
|
-
export declare function formatDate(date: Date, formatString: string): string;
|
|
37
|
-
//# sourceMappingURL=dateUtils.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dateUtils.d.ts","sourceRoot":"","sources":["../../../components/calendar/dateUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAItD;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAItD;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CA+FnE"}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Native date utility functions to replace date-fns
|
|
3
|
-
* Zero external dependencies
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* Add days to a date
|
|
7
|
-
*/
|
|
8
|
-
export function addDays(date, days) {
|
|
9
|
-
const result = new Date(date);
|
|
10
|
-
result.setDate(result.getDate() + days);
|
|
11
|
-
return result;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Subtract days from a date
|
|
15
|
-
*/
|
|
16
|
-
export function subDays(date, days) {
|
|
17
|
-
const result = new Date(date);
|
|
18
|
-
result.setDate(result.getDate() - days);
|
|
19
|
-
return result;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Format a date according to a format string
|
|
23
|
-
* Supports common tokens:
|
|
24
|
-
* - EEE: Short weekday name (Mon, Tue, etc.)
|
|
25
|
-
* - EEEE: Full weekday name (Monday, Tuesday, etc.)
|
|
26
|
-
* - MMM: Short month name (Jan, Feb, etc.)
|
|
27
|
-
* - MMMM: Full month name (January, February, etc.)
|
|
28
|
-
* - MM: Month as 2 digits (01-12)
|
|
29
|
-
* - M: Month as 1-2 digits (1-12)
|
|
30
|
-
* - dd: Day as 2 digits (01-31)
|
|
31
|
-
* - d: Day as 1-2 digits (1-31)
|
|
32
|
-
* - yyyy: Full year (2026)
|
|
33
|
-
* - yy: 2-digit year (26)
|
|
34
|
-
* - HH: Hour in 24h format with leading zero (00-23)
|
|
35
|
-
* - H: Hour in 24h format (0-23)
|
|
36
|
-
* - hh: Hour in 12h format with leading zero (01-12)
|
|
37
|
-
* - h: Hour in 12h format (1-12)
|
|
38
|
-
* - mm: Minutes with leading zero (00-59)
|
|
39
|
-
* - m: Minutes (0-59)
|
|
40
|
-
* - ss: Seconds with leading zero (00-59)
|
|
41
|
-
* - s: Seconds (0-59)
|
|
42
|
-
* - a: AM/PM
|
|
43
|
-
*/
|
|
44
|
-
export function formatDate(date, formatString) {
|
|
45
|
-
const weekdaysShort = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
46
|
-
const weekdaysFull = [
|
|
47
|
-
"Sunday",
|
|
48
|
-
"Monday",
|
|
49
|
-
"Tuesday",
|
|
50
|
-
"Wednesday",
|
|
51
|
-
"Thursday",
|
|
52
|
-
"Friday",
|
|
53
|
-
"Saturday",
|
|
54
|
-
];
|
|
55
|
-
const monthsShort = [
|
|
56
|
-
"Jan",
|
|
57
|
-
"Feb",
|
|
58
|
-
"Mar",
|
|
59
|
-
"Apr",
|
|
60
|
-
"May",
|
|
61
|
-
"Jun",
|
|
62
|
-
"Jul",
|
|
63
|
-
"Aug",
|
|
64
|
-
"Sep",
|
|
65
|
-
"Oct",
|
|
66
|
-
"Nov",
|
|
67
|
-
"Dec",
|
|
68
|
-
];
|
|
69
|
-
const monthsFull = [
|
|
70
|
-
"January",
|
|
71
|
-
"February",
|
|
72
|
-
"March",
|
|
73
|
-
"April",
|
|
74
|
-
"May",
|
|
75
|
-
"June",
|
|
76
|
-
"July",
|
|
77
|
-
"August",
|
|
78
|
-
"September",
|
|
79
|
-
"October",
|
|
80
|
-
"November",
|
|
81
|
-
"December",
|
|
82
|
-
];
|
|
83
|
-
const day = date.getDate();
|
|
84
|
-
const month = date.getMonth();
|
|
85
|
-
const year = date.getFullYear();
|
|
86
|
-
const weekday = date.getDay();
|
|
87
|
-
const hours = date.getHours();
|
|
88
|
-
const minutes = date.getMinutes();
|
|
89
|
-
const seconds = date.getSeconds();
|
|
90
|
-
const pad = (num, size = 2) => {
|
|
91
|
-
return num.toString().padStart(size, "0");
|
|
92
|
-
};
|
|
93
|
-
const tokens = {
|
|
94
|
-
EEEE: weekdaysFull[weekday],
|
|
95
|
-
EEE: weekdaysShort[weekday],
|
|
96
|
-
MMMM: monthsFull[month],
|
|
97
|
-
MMM: monthsShort[month],
|
|
98
|
-
MM: pad(month + 1),
|
|
99
|
-
M: (month + 1).toString(),
|
|
100
|
-
dd: pad(day),
|
|
101
|
-
d: day.toString(),
|
|
102
|
-
yyyy: year.toString(),
|
|
103
|
-
yy: year.toString().slice(-2),
|
|
104
|
-
HH: pad(hours),
|
|
105
|
-
H: hours.toString(),
|
|
106
|
-
hh: pad(hours % 12 || 12),
|
|
107
|
-
h: (hours % 12 || 12).toString(),
|
|
108
|
-
mm: pad(minutes),
|
|
109
|
-
m: minutes.toString(),
|
|
110
|
-
ss: pad(seconds),
|
|
111
|
-
s: seconds.toString(),
|
|
112
|
-
a: hours >= 12 ? "PM" : "AM",
|
|
113
|
-
};
|
|
114
|
-
let result = formatString;
|
|
115
|
-
// Sort by length descending to replace longer tokens first (e.g., MMMM before MMM)
|
|
116
|
-
const sortedTokens = Object.keys(tokens).sort((a, b) => b.length - a.length);
|
|
117
|
-
for (const token of sortedTokens) {
|
|
118
|
-
// Use a unique placeholder to avoid re-replacement issues
|
|
119
|
-
const placeholder = `\x00${token}\x00`;
|
|
120
|
-
result = result.replace(new RegExp(token, "g"), placeholder);
|
|
121
|
-
}
|
|
122
|
-
// Replace placeholders with actual values
|
|
123
|
-
for (const token of sortedTokens) {
|
|
124
|
-
const placeholder = `\x00${token}\x00`;
|
|
125
|
-
result = result.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), tokens[token]);
|
|
126
|
-
}
|
|
127
|
-
return result;
|
|
128
|
-
}
|