react-native-resource-calendar 1.0.19 β 1.0.21
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/README.md +39 -30
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +61 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +61 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,9 +10,6 @@ Expo compatibility.
|
|
|
10
10
|
- β
Multi-resource/multi-days timeline layout
|
|
11
11
|
- π¨ Customizable event slots (Body, TopRight)
|
|
12
12
|
- π± Smooth Reanimated drag-and-drop
|
|
13
|
-
- β° Timezone-aware time labels
|
|
14
|
-
- π§© Modular store (Zustand binding by default)
|
|
15
|
-
- ποΈ Theme support (font families, weights)
|
|
16
13
|
- πͺΆ Lightweight and Expo-ready
|
|
17
14
|
|
|
18
15
|
---
|
|
@@ -42,8 +39,7 @@ react-native-gesture-handler \
|
|
|
42
39
|
react-native-reanimated \
|
|
43
40
|
react-native-svg \
|
|
44
41
|
@shopify/flash-list \
|
|
45
|
-
@shopify/react-native-skia
|
|
46
|
-
expo-haptics
|
|
42
|
+
@shopify/react-native-skia
|
|
47
43
|
```
|
|
48
44
|
|
|
49
45
|
If youβre using bare React Native (not Expo), install them manually:
|
|
@@ -54,8 +50,19 @@ react-native-gesture-handler \
|
|
|
54
50
|
react-native-reanimated \
|
|
55
51
|
react-native-svg \
|
|
56
52
|
@shopify/flash-list \
|
|
57
|
-
@shopify/react-native-skia
|
|
58
|
-
|
|
53
|
+
@shopify/react-native-skia
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
π¦ Optional: Haptics Support (Expo Only)
|
|
57
|
+
|
|
58
|
+
Haptic feedback is optional.
|
|
59
|
+
If you want to enable vibration feedback when interacting with components, install the Expo Haptics package and set
|
|
60
|
+
enableHapticFeedback to true in your component config.
|
|
61
|
+
|
|
62
|
+
π¦ Install (Expo)
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npx expo install expo-haptics
|
|
59
66
|
```
|
|
60
67
|
|
|
61
68
|
---
|
|
@@ -251,28 +258,30 @@ export default function App() {
|
|
|
251
258
|
|
|
252
259
|
The `Calendar` component accepts a flexible set of props for customizing layout, theme, and interactivity.
|
|
253
260
|
|
|
254
|
-
| Prop | Type | Default | Description
|
|
255
|
-
|
|
256
|
-
| **`date`** | `Date` | `new Date()` | The anchor day shown in the timeline. In multi-day modes this is the **first** visible day.
|
|
257
|
-
| **`mode`** | `CalendarMode` (`'day' \| '3days' \| 'week'`) | `'day'` | Controls the column semantics. **day** = many resources for one day. **3days/week** = several days for one resource.
|
|
258
|
-
| **`activeResourceId`** | `number` | first `resources[0].id` | When `mode !== 'day'`, columns represent days for **this** resource.
|
|
259
|
-
| **`resources`** | `Array<Resource & { events: Event[]; disabledBlocks?: DisabledBlock[]; disableIntervals?: DisabledInterval[] }>` | **required** | Resource columns. Each resource includes its dayβs `events`, optional `disabledBlocks
|
|
260
|
-
| **`timezone`** | `string` | device time zone | Used for time labels and converting block taps to a `Date`.
|
|
261
|
-
| **`startMinutes`** | `number` | `0` | Start of visible timeline in minutes after midnight (e.g. `8 * 60` = 08:00).
|
|
262
|
-
| **`numberOfColumns`** | `number` | `3` | **Day mode only.** How many resource columns to show side-by-side. (In multi-day modes, the column count is fixed by the mode: 3 or 7.)
|
|
263
|
-
| **`hourHeight`** | `number` | `120` | Vertical density, px per hour. Affects drag/resize and scroll snap.
|
|
264
|
-
| **`snapIntervalInMinutes`** | `number` | `5` | Drag/resize snapping granularity (in minutes).
|
|
265
|
-
| **`overLappingLayoutMode`** | `LayoutMode` (`'stacked' \| 'columns'`) | `'stacked'` | Strategy to lay out overlapping events inside a column.
|
|
266
|
-
| **`theme`** | `CalendarTheme` | β | Typography & palette overrides.
|
|
267
|
-
| **`
|
|
268
|
-
| **`
|
|
269
|
-
| **`
|
|
270
|
-
| **`
|
|
271
|
-
| **`
|
|
272
|
-
| **`
|
|
273
|
-
| **`
|
|
274
|
-
| **`
|
|
275
|
-
| **`
|
|
261
|
+
| Prop | Type | Default | Description |
|
|
262
|
+
|-----------------------------|------------------------------------------------------------------------------------------------------------------|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
|
|
263
|
+
| **`date`** | `Date` | `new Date()` | The anchor day shown in the timeline. In multi-day modes this is the **first** visible day. |
|
|
264
|
+
| **`mode`** | `CalendarMode` (`'day' \| '3days' \| 'week'`) | `'day'` | Controls the column semantics. **day** = many resources for one day. **3days/week** = several days for one resource. |
|
|
265
|
+
| **`activeResourceId`** | `number` | first `resources[0].id` | When `mode !== 'day'`, columns represent days for **this** resource. |
|
|
266
|
+
| **`resources`** | `Array<Resource & { events: Event[]; disabledBlocks?: DisabledBlock[]; disableIntervals?: DisabledInterval[] }>` | **required** | Resource columns. Each resource includes its dayβs `events`, optional `disabledBlocks`, and `disableIntervals`. |
|
|
267
|
+
| **`timezone`** | `string` | device time zone | Used for time labels and converting block taps to a `Date`. |
|
|
268
|
+
| **`startMinutes`** | `number` | `0` | Start of visible timeline in minutes after midnight (e.g. `8 * 60` = 08:00). |
|
|
269
|
+
| **`numberOfColumns`** | `number` | `3` | **Day mode only.** How many resource columns to show side-by-side. (In multi-day modes, the column count is fixed by the mode: 3 or 7.) |
|
|
270
|
+
| **`hourHeight`** | `number` | `120` | Vertical density, px per hour. Affects drag/resize and scroll snap. |
|
|
271
|
+
| **`snapIntervalInMinutes`** | `number` | `5` | Drag/resize snapping granularity (in minutes). |
|
|
272
|
+
| **`overLappingLayoutMode`** | `LayoutMode` (`'stacked' \| 'columns'`) | `'stacked'` | Strategy to lay out overlapping events inside a column. |
|
|
273
|
+
| **`theme`** | `CalendarTheme` | β | Typography & palette overrides. |
|
|
274
|
+
| **`enableHapticFeedback`** | `boolean` | `false` | Enable haptic feedback. |
|
|
275
|
+
| **`eventSlots`** | `EventSlots` | β | Slot renderers to customize event content (e.g. `{ Body, TopRight }`). |
|
|
276
|
+
| **`eventStyleOverrides`** | `StyleOverrides \| ((event: Event) => StyleOverrides \| undefined)` | β | Per-event style override (object or function). |
|
|
277
|
+
| **`isEventSelected`** | `(event: Event) => boolean` | `() => false` | Marks which events are currently selected. |
|
|
278
|
+
| **`isEventDisabled`** | `(event: Event) => boolean` | `() => false` | Marks events as disabled (non-interactive). |
|
|
279
|
+
| **`onResourcePress`** | `(resource: Resource) => void` | β | Invoked when a resource header is pressed. |
|
|
280
|
+
| **`onBlockLongPress`** | `(resource: Resource, date: Date) => void` | β | Long-press on an empty block (grid). |
|
|
281
|
+
| **`onBlockTap`** | `(resource: Resource, date: Date) => void` | β | Tap on an empty block (grid). |
|
|
282
|
+
| **`onDisabledBlockPress`** | `(block: DisabledBlock) => void` | β | Tap on a disabled block (e.g., lunch). |
|
|
283
|
+
| **`onEventPress`** | `(event: Event) => void` | β | Tap on an event. |
|
|
284
|
+
| **`onEventLongPress`** | `(event: Event) => void` | β | Long-press on an event. The calendar also preps internal drag state here. |
|
|
276
285
|
|
|
277
286
|
---
|
|
278
287
|
|
|
@@ -329,7 +338,7 @@ type CalendarTheme = {
|
|
|
329
338
|
fontFamily?: string;
|
|
330
339
|
};
|
|
331
340
|
};
|
|
332
|
-
|
|
341
|
+
|
|
333
342
|
type CalendarMode = 'day' | '3days' | 'week';
|
|
334
343
|
```
|
|
335
344
|
|
package/dist/index.d.mts
CHANGED
|
@@ -86,9 +86,11 @@ interface CalendarProps {
|
|
|
86
86
|
hourHeight?: number;
|
|
87
87
|
onResourcePress?: (resource: Resource) => void;
|
|
88
88
|
onBlockLongPress?: (resource: Resource, date: Date) => void;
|
|
89
|
+
onBlockTap?: (resource: Resource, date: Date) => void;
|
|
89
90
|
onDisabledBlockPress?: (block: DisabledBlock) => void;
|
|
90
91
|
onEventPress?: (event: Event) => void;
|
|
91
92
|
onEventLongPress?: (event: Event) => void;
|
|
93
|
+
enableHapticFeedback?: boolean;
|
|
92
94
|
eventSlots?: EventSlots;
|
|
93
95
|
eventStyleOverrides?: StyleOverrides | ((event: Event) => StyleOverrides | undefined);
|
|
94
96
|
isEventSelected?: FlagFn;
|
package/dist/index.d.ts
CHANGED
|
@@ -86,9 +86,11 @@ interface CalendarProps {
|
|
|
86
86
|
hourHeight?: number;
|
|
87
87
|
onResourcePress?: (resource: Resource) => void;
|
|
88
88
|
onBlockLongPress?: (resource: Resource, date: Date) => void;
|
|
89
|
+
onBlockTap?: (resource: Resource, date: Date) => void;
|
|
89
90
|
onDisabledBlockPress?: (block: DisabledBlock) => void;
|
|
90
91
|
onEventPress?: (event: Event) => void;
|
|
91
92
|
onEventLongPress?: (event: Event) => void;
|
|
93
|
+
enableHapticFeedback?: boolean;
|
|
92
94
|
eventSlots?: EventSlots;
|
|
93
95
|
eventStyleOverrides?: StyleOverrides | ((event: Event) => StyleOverrides | undefined);
|
|
94
96
|
isEventSelected?: FlagFn;
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,6 @@ var reactNativeGestureHandler = require('react-native-gesture-handler');
|
|
|
5
5
|
var Animated2 = require('react-native-reanimated');
|
|
6
6
|
var reactNative = require('react-native');
|
|
7
7
|
var flashList = require('@shopify/flash-list');
|
|
8
|
-
var Haptics = require('expo-haptics');
|
|
9
8
|
var dateFnsTz = require('date-fns-tz');
|
|
10
9
|
var dateFns = require('date-fns');
|
|
11
10
|
var lodash = require('lodash');
|
|
@@ -39,7 +38,6 @@ function _interopNamespace(e) {
|
|
|
39
38
|
|
|
40
39
|
var React19__namespace = /*#__PURE__*/_interopNamespace(React19);
|
|
41
40
|
var Animated2__default = /*#__PURE__*/_interopDefault(Animated2);
|
|
42
|
-
var Haptics__namespace = /*#__PURE__*/_interopNamespace(Haptics);
|
|
43
41
|
var Svg__default = /*#__PURE__*/_interopDefault(Svg);
|
|
44
42
|
|
|
45
43
|
// src/components/Calendar.tsx
|
|
@@ -747,6 +745,7 @@ function StaffAvatar({
|
|
|
747
745
|
}
|
|
748
746
|
var EventGridBlocksSkia = ({
|
|
749
747
|
handleBlockPress,
|
|
748
|
+
handleBlockLongPress,
|
|
750
749
|
hourHeight,
|
|
751
750
|
APPOINTMENT_BLOCK_WIDTH
|
|
752
751
|
}) => {
|
|
@@ -788,6 +787,16 @@ var EventGridBlocksSkia = ({
|
|
|
788
787
|
},
|
|
789
788
|
[handleBlockPress, timeLabels]
|
|
790
789
|
);
|
|
790
|
+
const onSlotLongPress = React19__namespace.useCallback(
|
|
791
|
+
(row) => {
|
|
792
|
+
setPressedRow(null);
|
|
793
|
+
const slot = timeLabels[row];
|
|
794
|
+
if (slot) {
|
|
795
|
+
handleBlockLongPress(slot);
|
|
796
|
+
}
|
|
797
|
+
},
|
|
798
|
+
[timeLabels, handleBlockLongPress]
|
|
799
|
+
);
|
|
791
800
|
const onPressBegin = React19__namespace.useCallback((row) => {
|
|
792
801
|
setPressedRow(row);
|
|
793
802
|
}, []);
|
|
@@ -797,14 +806,31 @@ var EventGridBlocksSkia = ({
|
|
|
797
806
|
const longPressGesture = reactNativeGestureHandler.Gesture.LongPress().onBegin((e) => {
|
|
798
807
|
"worklet";
|
|
799
808
|
Animated2.runOnJS(onPressBegin)(Math.floor(e.y / rowHeight));
|
|
809
|
+
}).onTouchesUp(() => {
|
|
810
|
+
"worklet";
|
|
811
|
+
Animated2.runOnJS(onTouchesUp)();
|
|
812
|
+
}).onEnd((e) => {
|
|
813
|
+
"worklet";
|
|
814
|
+
Animated2.runOnJS(onSlotLongPress)(Math.floor(e.y / rowHeight));
|
|
815
|
+
}).onFinalize(() => {
|
|
816
|
+
"worklet";
|
|
817
|
+
Animated2.runOnJS(onTouchesUp)();
|
|
818
|
+
});
|
|
819
|
+
const tapGesture = reactNativeGestureHandler.Gesture.Tap().onBegin((e) => {
|
|
820
|
+
"worklet";
|
|
821
|
+
Animated2.runOnJS(onPressBegin)(Math.floor(e.y / rowHeight));
|
|
800
822
|
}).onEnd((e) => {
|
|
801
823
|
"worklet";
|
|
802
824
|
Animated2.runOnJS(onSlotPress)(Math.floor(e.y / rowHeight));
|
|
825
|
+
}).onTouchesUp(() => {
|
|
826
|
+
"worklet";
|
|
827
|
+
Animated2.runOnJS(onTouchesUp)();
|
|
803
828
|
}).onFinalize(() => {
|
|
804
829
|
"worklet";
|
|
805
830
|
Animated2.runOnJS(onTouchesUp)();
|
|
806
831
|
});
|
|
807
|
-
|
|
832
|
+
const composedGesture = reactNativeGestureHandler.Gesture.Race(longPressGesture, tapGesture);
|
|
833
|
+
return /* @__PURE__ */ React19__namespace.createElement(reactNativeGestureHandler.GestureDetector, { gesture: composedGesture }, /* @__PURE__ */ React19__namespace.createElement(reactNative.View, null, /* @__PURE__ */ React19__namespace.createElement(reactNativeSkia.Canvas, { style: { width: APPOINTMENT_BLOCK_WIDTH, height: segmentHeight } }, firstRects.map(({ x, y, width: w, height: h, row }, idx) => /* @__PURE__ */ React19__namespace.createElement(React19__namespace.Fragment, { key: idx }, /* @__PURE__ */ React19__namespace.createElement(
|
|
808
834
|
reactNativeSkia.Rect,
|
|
809
835
|
{
|
|
810
836
|
x,
|
|
@@ -1053,7 +1079,8 @@ var EventBlock = React19__namespace.default.memo(({
|
|
|
1053
1079
|
fontSize: getTextSize(hourHeight),
|
|
1054
1080
|
pointerEvents: "none",
|
|
1055
1081
|
padding: 0,
|
|
1056
|
-
margin: 0
|
|
1082
|
+
margin: 0,
|
|
1083
|
+
color: "black"
|
|
1057
1084
|
}, resolved?.time],
|
|
1058
1085
|
defaultValue: `${start} - ${end}`
|
|
1059
1086
|
}
|
|
@@ -1172,7 +1199,8 @@ var DraggableEvent = ({
|
|
|
1172
1199
|
fontSize: getTextSize(hourHeight),
|
|
1173
1200
|
pointerEvents: "none",
|
|
1174
1201
|
padding: 0,
|
|
1175
|
-
margin: 0
|
|
1202
|
+
margin: 0,
|
|
1203
|
+
color: "black"
|
|
1176
1204
|
}, resolved?.time],
|
|
1177
1205
|
defaultValue: initialDisplayTime,
|
|
1178
1206
|
animatedProps: animatedTimeProps
|
|
@@ -1346,9 +1374,11 @@ var CalendarInner = (props) => {
|
|
|
1346
1374
|
resources,
|
|
1347
1375
|
onResourcePress,
|
|
1348
1376
|
onBlockLongPress,
|
|
1377
|
+
onBlockTap,
|
|
1349
1378
|
onEventPress,
|
|
1350
1379
|
onEventLongPress,
|
|
1351
1380
|
onDisabledBlockPress,
|
|
1381
|
+
enableHapticFeedback = false,
|
|
1352
1382
|
eventSlots,
|
|
1353
1383
|
eventStyleOverrides,
|
|
1354
1384
|
overLappingLayoutMode = "stacked",
|
|
@@ -1465,9 +1495,19 @@ var CalendarInner = (props) => {
|
|
|
1465
1495
|
const startedX = Animated2.useSharedValue(0);
|
|
1466
1496
|
const startedY = Animated2.useSharedValue(0);
|
|
1467
1497
|
const touchY = Animated2.useSharedValue(0);
|
|
1468
|
-
const triggerHaptic = React19.useCallback(
|
|
1469
|
-
|
|
1470
|
-
|
|
1498
|
+
const triggerHaptic = React19.useCallback(
|
|
1499
|
+
async (style = "Light") => {
|
|
1500
|
+
try {
|
|
1501
|
+
const Haptics = await import('expo-haptics');
|
|
1502
|
+
const feedbackStyle = Haptics.ImpactFeedbackStyle[style];
|
|
1503
|
+
if (enableHapticFeedback)
|
|
1504
|
+
await Haptics.impactAsync(feedbackStyle);
|
|
1505
|
+
} catch (e) {
|
|
1506
|
+
console.log("Haptics not available, skipping...");
|
|
1507
|
+
}
|
|
1508
|
+
},
|
|
1509
|
+
[enableHapticFeedback]
|
|
1510
|
+
);
|
|
1471
1511
|
const resourceIds = React19.useMemo(() => {
|
|
1472
1512
|
const ids = resources?.map((item) => item?.id) || [];
|
|
1473
1513
|
if (JSON.stringify(prevResourceIdsRef.current) !== JSON.stringify(ids)) {
|
|
@@ -1628,7 +1668,7 @@ var CalendarInner = (props) => {
|
|
|
1628
1668
|
const increment = APPOINTMENT_BLOCK_WIDTH * Math.sign(autoScrollXSpeed.value);
|
|
1629
1669
|
const newScrollX = scrollX.value + increment;
|
|
1630
1670
|
Animated2.runOnJS(scrollListTo)(newScrollX);
|
|
1631
|
-
Animated2.runOnJS(
|
|
1671
|
+
Animated2.runOnJS(triggerHaptic)("Medium");
|
|
1632
1672
|
}
|
|
1633
1673
|
});
|
|
1634
1674
|
Animated2.useFrameCallback(() => {
|
|
@@ -1662,7 +1702,7 @@ var CalendarInner = (props) => {
|
|
|
1662
1702
|
const scrollDiff = Math.abs(newScrollY - lastHapticScrollY.value);
|
|
1663
1703
|
if (scrollDiff >= snapInterval) {
|
|
1664
1704
|
lastHapticScrollY.value = newScrollY;
|
|
1665
|
-
Animated2.runOnJS(
|
|
1705
|
+
Animated2.runOnJS(triggerHaptic)("Medium");
|
|
1666
1706
|
}
|
|
1667
1707
|
});
|
|
1668
1708
|
React19.useEffect(() => {
|
|
@@ -1696,7 +1736,7 @@ var CalendarInner = (props) => {
|
|
|
1696
1736
|
eventHeight.value = initialHeight;
|
|
1697
1737
|
setSelectedEvent(event);
|
|
1698
1738
|
requestAnimationFrame(() => setDragReady(true));
|
|
1699
|
-
|
|
1739
|
+
Animated2.runOnJS(triggerHaptic)("Medium");
|
|
1700
1740
|
};
|
|
1701
1741
|
}, []);
|
|
1702
1742
|
const internalStableOnLongPress = React19.useCallback((e) => {
|
|
@@ -1719,12 +1759,18 @@ var CalendarInner = (props) => {
|
|
|
1719
1759
|
}
|
|
1720
1760
|
}
|
|
1721
1761
|
});
|
|
1722
|
-
const
|
|
1723
|
-
|
|
1762
|
+
const handleBlockLongPress = React19.useCallback((resourceId, time) => {
|
|
1763
|
+
Animated2.runOnJS(triggerHaptic)("Medium");
|
|
1724
1764
|
const resource = resources.find((r) => r.id === resourceId);
|
|
1725
1765
|
if (onBlockLongPress)
|
|
1726
1766
|
onBlockLongPress(resource, new Date(time));
|
|
1727
1767
|
}, [resources, onBlockLongPress]);
|
|
1768
|
+
const handleBlockPress = React19.useCallback((resourceId, time) => {
|
|
1769
|
+
Animated2.runOnJS(triggerHaptic)("Medium");
|
|
1770
|
+
const resource = resources.find((r) => r.id === resourceId);
|
|
1771
|
+
if (onBlockTap)
|
|
1772
|
+
onBlockTap(resource, new Date(time));
|
|
1773
|
+
}, [resources, onBlockTap]);
|
|
1728
1774
|
React19.useEffect(() => {
|
|
1729
1775
|
const handleOrientationChange = () => {
|
|
1730
1776
|
if (selectedEvent) {
|
|
@@ -1748,7 +1794,8 @@ var CalendarInner = (props) => {
|
|
|
1748
1794
|
{
|
|
1749
1795
|
hourHeight,
|
|
1750
1796
|
APPOINTMENT_BLOCK_WIDTH,
|
|
1751
|
-
handleBlockPress: (time) => handleBlockPress(rid, combineDateAndTime(dayDate ?? dateRef.current, time))
|
|
1797
|
+
handleBlockPress: (time) => handleBlockPress(rid, combineDateAndTime(dayDate ?? dateRef.current, time)),
|
|
1798
|
+
handleBlockLongPress: (time) => handleBlockLongPress(rid, combineDateAndTime(dayDate ?? dateRef.current, time))
|
|
1752
1799
|
}
|
|
1753
1800
|
), /* @__PURE__ */ React19__namespace.default.createElement(
|
|
1754
1801
|
DisabledIntervals_default,
|