@sunsama/event-calendar 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +105 -0
  3. package/lib/commonjs/components/all-day-events.js +117 -0
  4. package/lib/commonjs/components/all-day-events.js.map +1 -0
  5. package/lib/commonjs/components/background-hours-content.js +43 -0
  6. package/lib/commonjs/components/background-hours-content.js.map +1 -0
  7. package/lib/commonjs/components/background-hours-layout.js +57 -0
  8. package/lib/commonjs/components/background-hours-layout.js.map +1 -0
  9. package/lib/commonjs/components/drag-bar.js +84 -0
  10. package/lib/commonjs/components/drag-bar.js.map +1 -0
  11. package/lib/commonjs/components/edit-event-container.js +114 -0
  12. package/lib/commonjs/components/edit-event-container.js.map +1 -0
  13. package/lib/commonjs/components/event-container.js +37 -0
  14. package/lib/commonjs/components/event-container.js.map +1 -0
  15. package/lib/commonjs/components/new-event-container.js +73 -0
  16. package/lib/commonjs/components/new-event-container.js.map +1 -0
  17. package/lib/commonjs/components/time-indicator.js +64 -0
  18. package/lib/commonjs/components/time-indicator.js.map +1 -0
  19. package/lib/commonjs/components/timed-event-container.js +91 -0
  20. package/lib/commonjs/components/timed-event-container.js.map +1 -0
  21. package/lib/commonjs/components/timed-events.js +68 -0
  22. package/lib/commonjs/components/timed-events.js.map +1 -0
  23. package/lib/commonjs/components/zoom-provider.js +109 -0
  24. package/lib/commonjs/components/zoom-provider.js.map +1 -0
  25. package/lib/commonjs/enums.js +2 -0
  26. package/lib/commonjs/enums.js.map +1 -0
  27. package/lib/commonjs/hooks/use-cloned-events.js +25 -0
  28. package/lib/commonjs/hooks/use-cloned-events.js.map +1 -0
  29. package/lib/commonjs/hooks/use-events-layout.js +34 -0
  30. package/lib/commonjs/hooks/use-events-layout.js.map +1 -0
  31. package/lib/commonjs/hooks/use-is-editing.js +83 -0
  32. package/lib/commonjs/hooks/use-is-editing.js.map +1 -0
  33. package/lib/commonjs/index.js +129 -0
  34. package/lib/commonjs/index.js.map +1 -0
  35. package/lib/commonjs/types.js +24 -0
  36. package/lib/commonjs/types.js.map +1 -0
  37. package/lib/commonjs/utils/calendar-layout.js +113 -0
  38. package/lib/commonjs/utils/calendar-layout.js.map +1 -0
  39. package/lib/commonjs/utils/compute-positioning.js +33 -0
  40. package/lib/commonjs/utils/compute-positioning.js.map +1 -0
  41. package/lib/commonjs/utils/date-utils.js +152 -0
  42. package/lib/commonjs/utils/date-utils.js.map +1 -0
  43. package/lib/commonjs/utils/double-tap-reset-zoom-gesture.js +19 -0
  44. package/lib/commonjs/utils/double-tap-reset-zoom-gesture.js.map +1 -0
  45. package/lib/commonjs/utils/generate-event-layouts.js +198 -0
  46. package/lib/commonjs/utils/generate-event-layouts.js.map +1 -0
  47. package/lib/commonjs/utils/globals.js +11 -0
  48. package/lib/commonjs/utils/globals.js.map +1 -0
  49. package/lib/commonjs/utils/pan-edit-event-gesture.js +41 -0
  50. package/lib/commonjs/utils/pan-edit-event-gesture.js.map +1 -0
  51. package/lib/module/components/all-day-events.js +110 -0
  52. package/lib/module/components/all-day-events.js.map +1 -0
  53. package/lib/module/components/background-hours-content.js +37 -0
  54. package/lib/module/components/background-hours-content.js.map +1 -0
  55. package/lib/module/components/background-hours-layout.js +51 -0
  56. package/lib/module/components/background-hours-layout.js.map +1 -0
  57. package/lib/module/components/drag-bar.js +78 -0
  58. package/lib/module/components/drag-bar.js.map +1 -0
  59. package/lib/module/components/edit-event-container.js +107 -0
  60. package/lib/module/components/edit-event-container.js.map +1 -0
  61. package/lib/module/components/event-container.js +33 -0
  62. package/lib/module/components/event-container.js.map +1 -0
  63. package/lib/module/components/new-event-container.js +67 -0
  64. package/lib/module/components/new-event-container.js.map +1 -0
  65. package/lib/module/components/time-indicator.js +57 -0
  66. package/lib/module/components/time-indicator.js.map +1 -0
  67. package/lib/module/components/timed-event-container.js +84 -0
  68. package/lib/module/components/timed-event-container.js.map +1 -0
  69. package/lib/module/components/timed-events.js +63 -0
  70. package/lib/module/components/timed-events.js.map +1 -0
  71. package/lib/module/components/zoom-provider.js +102 -0
  72. package/lib/module/components/zoom-provider.js.map +1 -0
  73. package/lib/module/enums.js +2 -0
  74. package/lib/module/enums.js.map +1 -0
  75. package/lib/module/hooks/use-cloned-events.js +21 -0
  76. package/lib/module/hooks/use-cloned-events.js.map +1 -0
  77. package/lib/module/hooks/use-events-layout.js +29 -0
  78. package/lib/module/hooks/use-events-layout.js.map +1 -0
  79. package/lib/module/hooks/use-is-editing.js +75 -0
  80. package/lib/module/hooks/use-is-editing.js.map +1 -0
  81. package/lib/module/index.js +124 -0
  82. package/lib/module/index.js.map +1 -0
  83. package/lib/module/types.js +20 -0
  84. package/lib/module/types.js.map +1 -0
  85. package/lib/module/utils/calendar-layout.js +108 -0
  86. package/lib/module/utils/calendar-layout.js.map +1 -0
  87. package/lib/module/utils/compute-positioning.js +28 -0
  88. package/lib/module/utils/compute-positioning.js.map +1 -0
  89. package/lib/module/utils/date-utils.js +139 -0
  90. package/lib/module/utils/date-utils.js.map +1 -0
  91. package/lib/module/utils/double-tap-reset-zoom-gesture.js +15 -0
  92. package/lib/module/utils/double-tap-reset-zoom-gesture.js.map +1 -0
  93. package/lib/module/utils/generate-event-layouts.js +192 -0
  94. package/lib/module/utils/generate-event-layouts.js.map +1 -0
  95. package/lib/module/utils/globals.js +7 -0
  96. package/lib/module/utils/globals.js.map +1 -0
  97. package/lib/module/utils/pan-edit-event-gesture.js +37 -0
  98. package/lib/module/utils/pan-edit-event-gesture.js.map +1 -0
  99. package/lib/typescript/commonjs/components/all-day-events.d.ts +3 -0
  100. package/lib/typescript/commonjs/components/all-day-events.d.ts.map +1 -0
  101. package/lib/typescript/commonjs/components/background-hours-content.d.ts +7 -0
  102. package/lib/typescript/commonjs/components/background-hours-content.d.ts.map +1 -0
  103. package/lib/typescript/commonjs/components/background-hours-layout.d.ts +7 -0
  104. package/lib/typescript/commonjs/components/background-hours-layout.d.ts.map +1 -0
  105. package/lib/typescript/commonjs/components/drag-bar.d.ts +14 -0
  106. package/lib/typescript/commonjs/components/drag-bar.d.ts.map +1 -0
  107. package/lib/typescript/commonjs/components/edit-event-container.d.ts +7 -0
  108. package/lib/typescript/commonjs/components/edit-event-container.d.ts.map +1 -0
  109. package/lib/typescript/commonjs/components/event-container.d.ts +7 -0
  110. package/lib/typescript/commonjs/components/event-container.d.ts.map +1 -0
  111. package/lib/typescript/commonjs/components/new-event-container.d.ts +3 -0
  112. package/lib/typescript/commonjs/components/new-event-container.d.ts.map +1 -0
  113. package/lib/typescript/commonjs/components/time-indicator.d.ts +3 -0
  114. package/lib/typescript/commonjs/components/time-indicator.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/components/timed-event-container.d.ts +9 -0
  116. package/lib/typescript/commonjs/components/timed-event-container.d.ts.map +1 -0
  117. package/lib/typescript/commonjs/components/timed-events.d.ts +7 -0
  118. package/lib/typescript/commonjs/components/timed-events.d.ts.map +1 -0
  119. package/lib/typescript/commonjs/components/zoom-provider.d.ts +7 -0
  120. package/lib/typescript/commonjs/components/zoom-provider.d.ts.map +1 -0
  121. package/lib/typescript/commonjs/enums.d.ts +2 -0
  122. package/lib/typescript/commonjs/enums.d.ts.map +1 -0
  123. package/lib/typescript/commonjs/hooks/use-cloned-events.d.ts +11 -0
  124. package/lib/typescript/commonjs/hooks/use-cloned-events.d.ts.map +1 -0
  125. package/lib/typescript/commonjs/hooks/use-events-layout.d.ts +13 -0
  126. package/lib/typescript/commonjs/hooks/use-events-layout.d.ts.map +1 -0
  127. package/lib/typescript/commonjs/hooks/use-is-editing.d.ts +17 -0
  128. package/lib/typescript/commonjs/hooks/use-is-editing.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/index.d.ts +27 -0
  130. package/lib/typescript/commonjs/index.d.ts.map +1 -0
  131. package/lib/typescript/commonjs/package.json +1 -0
  132. package/lib/typescript/commonjs/types.d.ts +128 -0
  133. package/lib/typescript/commonjs/types.d.ts.map +1 -0
  134. package/lib/typescript/commonjs/utils/__tests___/compute-positioning.test.d.ts +2 -0
  135. package/lib/typescript/commonjs/utils/__tests___/compute-positioning.test.d.ts.map +1 -0
  136. package/lib/typescript/commonjs/utils/__tests___/date-utils.test.d.ts +2 -0
  137. package/lib/typescript/commonjs/utils/__tests___/date-utils.test.d.ts.map +1 -0
  138. package/lib/typescript/commonjs/utils/__tests___/generate-event-layout.test.d.ts +2 -0
  139. package/lib/typescript/commonjs/utils/__tests___/generate-event-layout.test.d.ts.map +1 -0
  140. package/lib/typescript/commonjs/utils/calendar-layout.d.ts +36 -0
  141. package/lib/typescript/commonjs/utils/calendar-layout.d.ts.map +1 -0
  142. package/lib/typescript/commonjs/utils/compute-positioning.d.ts +10 -0
  143. package/lib/typescript/commonjs/utils/compute-positioning.d.ts.map +1 -0
  144. package/lib/typescript/commonjs/utils/date-utils.d.ts +30 -0
  145. package/lib/typescript/commonjs/utils/date-utils.d.ts.map +1 -0
  146. package/lib/typescript/commonjs/utils/double-tap-reset-zoom-gesture.d.ts +5 -0
  147. package/lib/typescript/commonjs/utils/double-tap-reset-zoom-gesture.d.ts.map +1 -0
  148. package/lib/typescript/commonjs/utils/generate-event-layouts.d.ts +15 -0
  149. package/lib/typescript/commonjs/utils/generate-event-layouts.d.ts.map +1 -0
  150. package/lib/typescript/commonjs/utils/globals.d.ts +5 -0
  151. package/lib/typescript/commonjs/utils/globals.d.ts.map +1 -0
  152. package/lib/typescript/commonjs/utils/pan-edit-event-gesture.d.ts +6 -0
  153. package/lib/typescript/commonjs/utils/pan-edit-event-gesture.d.ts.map +1 -0
  154. package/lib/typescript/module/components/all-day-events.d.ts +3 -0
  155. package/lib/typescript/module/components/all-day-events.d.ts.map +1 -0
  156. package/lib/typescript/module/components/background-hours-content.d.ts +7 -0
  157. package/lib/typescript/module/components/background-hours-content.d.ts.map +1 -0
  158. package/lib/typescript/module/components/background-hours-layout.d.ts +7 -0
  159. package/lib/typescript/module/components/background-hours-layout.d.ts.map +1 -0
  160. package/lib/typescript/module/components/drag-bar.d.ts +14 -0
  161. package/lib/typescript/module/components/drag-bar.d.ts.map +1 -0
  162. package/lib/typescript/module/components/edit-event-container.d.ts +7 -0
  163. package/lib/typescript/module/components/edit-event-container.d.ts.map +1 -0
  164. package/lib/typescript/module/components/event-container.d.ts +7 -0
  165. package/lib/typescript/module/components/event-container.d.ts.map +1 -0
  166. package/lib/typescript/module/components/new-event-container.d.ts +3 -0
  167. package/lib/typescript/module/components/new-event-container.d.ts.map +1 -0
  168. package/lib/typescript/module/components/time-indicator.d.ts +3 -0
  169. package/lib/typescript/module/components/time-indicator.d.ts.map +1 -0
  170. package/lib/typescript/module/components/timed-event-container.d.ts +9 -0
  171. package/lib/typescript/module/components/timed-event-container.d.ts.map +1 -0
  172. package/lib/typescript/module/components/timed-events.d.ts +7 -0
  173. package/lib/typescript/module/components/timed-events.d.ts.map +1 -0
  174. package/lib/typescript/module/components/zoom-provider.d.ts +7 -0
  175. package/lib/typescript/module/components/zoom-provider.d.ts.map +1 -0
  176. package/lib/typescript/module/enums.d.ts +2 -0
  177. package/lib/typescript/module/enums.d.ts.map +1 -0
  178. package/lib/typescript/module/hooks/use-cloned-events.d.ts +11 -0
  179. package/lib/typescript/module/hooks/use-cloned-events.d.ts.map +1 -0
  180. package/lib/typescript/module/hooks/use-events-layout.d.ts +13 -0
  181. package/lib/typescript/module/hooks/use-events-layout.d.ts.map +1 -0
  182. package/lib/typescript/module/hooks/use-is-editing.d.ts +17 -0
  183. package/lib/typescript/module/hooks/use-is-editing.d.ts.map +1 -0
  184. package/lib/typescript/module/index.d.ts +27 -0
  185. package/lib/typescript/module/index.d.ts.map +1 -0
  186. package/lib/typescript/module/package.json +1 -0
  187. package/lib/typescript/module/types.d.ts +128 -0
  188. package/lib/typescript/module/types.d.ts.map +1 -0
  189. package/lib/typescript/module/utils/__tests___/compute-positioning.test.d.ts +2 -0
  190. package/lib/typescript/module/utils/__tests___/compute-positioning.test.d.ts.map +1 -0
  191. package/lib/typescript/module/utils/__tests___/date-utils.test.d.ts +2 -0
  192. package/lib/typescript/module/utils/__tests___/date-utils.test.d.ts.map +1 -0
  193. package/lib/typescript/module/utils/__tests___/generate-event-layout.test.d.ts +2 -0
  194. package/lib/typescript/module/utils/__tests___/generate-event-layout.test.d.ts.map +1 -0
  195. package/lib/typescript/module/utils/calendar-layout.d.ts +36 -0
  196. package/lib/typescript/module/utils/calendar-layout.d.ts.map +1 -0
  197. package/lib/typescript/module/utils/compute-positioning.d.ts +10 -0
  198. package/lib/typescript/module/utils/compute-positioning.d.ts.map +1 -0
  199. package/lib/typescript/module/utils/date-utils.d.ts +30 -0
  200. package/lib/typescript/module/utils/date-utils.d.ts.map +1 -0
  201. package/lib/typescript/module/utils/double-tap-reset-zoom-gesture.d.ts +5 -0
  202. package/lib/typescript/module/utils/double-tap-reset-zoom-gesture.d.ts.map +1 -0
  203. package/lib/typescript/module/utils/generate-event-layouts.d.ts +15 -0
  204. package/lib/typescript/module/utils/generate-event-layouts.d.ts.map +1 -0
  205. package/lib/typescript/module/utils/globals.d.ts +5 -0
  206. package/lib/typescript/module/utils/globals.d.ts.map +1 -0
  207. package/lib/typescript/module/utils/pan-edit-event-gesture.d.ts +6 -0
  208. package/lib/typescript/module/utils/pan-edit-event-gesture.d.ts.map +1 -0
  209. package/package.json +195 -0
  210. package/src/components/all-day-events.tsx +134 -0
  211. package/src/components/background-hours-content.tsx +51 -0
  212. package/src/components/background-hours-layout.tsx +61 -0
  213. package/src/components/drag-bar.tsx +120 -0
  214. package/src/components/edit-event-container.tsx +158 -0
  215. package/src/components/event-container.tsx +44 -0
  216. package/src/components/new-event-container.tsx +90 -0
  217. package/src/components/time-indicator.tsx +72 -0
  218. package/src/components/timed-event-container.tsx +124 -0
  219. package/src/components/timed-events.tsx +72 -0
  220. package/src/components/zoom-provider.tsx +146 -0
  221. package/src/enums.ts +0 -0
  222. package/src/hooks/use-cloned-events.ts +26 -0
  223. package/src/hooks/use-events-layout.ts +55 -0
  224. package/src/hooks/use-is-editing.tsx +109 -0
  225. package/src/index.tsx +165 -0
  226. package/src/types.ts +163 -0
  227. package/src/utils/__tests___/compute-positioning.test.ts +255 -0
  228. package/src/utils/__tests___/date-utils.test.ts +41 -0
  229. package/src/utils/__tests___/generate-event-layout.test.ts +277 -0
  230. package/src/utils/calendar-layout.ts +139 -0
  231. package/src/utils/compute-positioning.ts +44 -0
  232. package/src/utils/date-utils.ts +238 -0
  233. package/src/utils/double-tap-reset-zoom-gesture.ts +23 -0
  234. package/src/utils/generate-event-layouts.ts +314 -0
  235. package/src/utils/globals.ts +8 -0
  236. package/src/utils/pan-edit-event-gesture.ts +64 -0
@@ -0,0 +1,124 @@
1
+ import Animated, {
2
+ runOnJS,
3
+ useAnimatedReaction,
4
+ useAnimatedStyle,
5
+ useSharedValue,
6
+ } from "react-native-reanimated";
7
+ import { ConfigProvider } from "src/utils/globals";
8
+ import { RefObject, useCallback, useContext, useMemo } from "react";
9
+ import { Gesture, GestureDetector } from "react-native-gesture-handler";
10
+ import { StyleSheet, View } from "react-native";
11
+ import { useIsEditing } from "src/hooks/use-is-editing";
12
+ import gesturePan from "src/utils/pan-edit-event-gesture";
13
+ import doubleTapGesture from "src/utils/double-tap-reset-zoom-gesture";
14
+ import { EventExtend, PartDayEventLayoutType } from "src/types";
15
+
16
+ type TimedEventContainerProps = {
17
+ layout: PartDayEventLayoutType;
18
+ refNewEvent: RefObject<any>;
19
+ };
20
+
21
+ const TimedEventContainer = ({
22
+ layout,
23
+ refNewEvent,
24
+ }: TimedEventContainerProps) => {
25
+ const { currentY, setIsEditing, isEditing } = useIsEditing();
26
+ const {
27
+ onPressEvent,
28
+ maximumHour,
29
+ fiveMinuteInterval,
30
+ zoomLevel,
31
+ renderEvent,
32
+ initialZoomLevel,
33
+ onZoomChange,
34
+ } = useContext(ConfigProvider);
35
+
36
+ const height = useSharedValue(0);
37
+ const top = useSharedValue(0);
38
+
39
+ const startEditing = useCallback(() => {
40
+ setIsEditing(layout);
41
+ }, [layout, setIsEditing]);
42
+
43
+ const gestureTap = Gesture.Tap()
44
+ .enabled(!isEditing)
45
+ .onStart(() => {
46
+ if (onPressEvent) {
47
+ runOnJS(onPressEvent)(layout.event);
48
+ }
49
+ });
50
+
51
+ const startY = useSharedValue(0);
52
+
53
+ const gestures = Gesture.Exclusive(
54
+ doubleTapGesture(zoomLevel, initialZoomLevel, onZoomChange),
55
+ gestureTap,
56
+ gesturePan(
57
+ startY,
58
+ top,
59
+ currentY,
60
+ zoomLevel,
61
+ maximumHour,
62
+ height,
63
+ refNewEvent,
64
+ fiveMinuteInterval,
65
+ isEditing,
66
+ startEditing
67
+ ).activateAfterLongPress(500)
68
+ );
69
+
70
+ useAnimatedReaction(
71
+ () => zoomLevel.value,
72
+ (newZoomLevel) => {
73
+ height.value = newZoomLevel * layout.position.height;
74
+ },
75
+ [layout.position.height]
76
+ );
77
+
78
+ useAnimatedReaction(
79
+ () => zoomLevel.value,
80
+ (newZoomLevel) => {
81
+ top.value = newZoomLevel * layout.position.top;
82
+ },
83
+ [layout.position.top]
84
+ );
85
+
86
+ const render = useMemo(
87
+ () => renderEvent(layout.event, EventExtend.None, height),
88
+ [height, layout.event, renderEvent]
89
+ );
90
+
91
+ const stylePosition = useAnimatedStyle(() => {
92
+ const basePosition: any = {
93
+ position: "absolute",
94
+ height: height.value,
95
+ top: top.value,
96
+ opacity: 1,
97
+ width: layout.position.width,
98
+ marginLeft: layout.position.marginLeft,
99
+
100
+ // This is to prevent the event from being clickable while editing
101
+ pointerEvents: isEditing ? "none" : "auto",
102
+ };
103
+
104
+ if (isEditing?.event.id === layout.event.id) {
105
+ basePosition.opacity = 0.5;
106
+ }
107
+
108
+ return basePosition;
109
+ }, [layout, isEditing]);
110
+
111
+ return (
112
+ <GestureDetector gesture={gestures}>
113
+ <Animated.View style={stylePosition}>
114
+ <View style={styles.hairline}>{render}</View>
115
+ </Animated.View>
116
+ </GestureDetector>
117
+ );
118
+ };
119
+
120
+ const styles = StyleSheet.create({
121
+ hairline: { marginHorizontal: StyleSheet.hairlineWidth, flex: 1 },
122
+ });
123
+
124
+ export default TimedEventContainer;
@@ -0,0 +1,72 @@
1
+ import { StyleSheet, View } from "react-native";
2
+ import { RefObject, useContext, useMemo } from "react";
3
+ import { ConfigProvider, TOP_MARGIN_PIXEL_OFFSET } from "src/utils/globals";
4
+ import BackgroundHoursLayout from "src/components/background-hours-layout";
5
+ import { generatePrefabHours } from "src/utils/date-utils";
6
+ import BackgroundHoursContent from "src/components/background-hours-content";
7
+ import TimedEventContainer from "src/components/timed-event-container";
8
+ import TimeIndicator from "src/components/time-indicator";
9
+ import NewEventContainer from "src/components/new-event-container";
10
+ import EditEventContainer from "src/components/edit-event-container";
11
+
12
+ type TimedEventsProps = {
13
+ refNewEvent: RefObject<any>;
14
+ };
15
+
16
+ const TimedEvents = ({ refNewEvent }: TimedEventsProps) => {
17
+ const {
18
+ theme,
19
+ canCreateEvents,
20
+ canEditEvent,
21
+ timeFormat,
22
+ layout,
23
+ showTimeIndicator,
24
+ extraTimedComponents,
25
+ zoomLevel,
26
+ } = useContext(ConfigProvider);
27
+ const hours = useMemo(() => generatePrefabHours(timeFormat), [timeFormat]);
28
+
29
+ const extraRender = useMemo(
30
+ () => extraTimedComponents?.(zoomLevel) || null,
31
+ [extraTimedComponents, zoomLevel]
32
+ );
33
+
34
+ return (
35
+ <View style={[styles.container, theme?.timedEventsContainer]}>
36
+ <BackgroundHoursLayout hours={hours} />
37
+ <View style={styles.backgroundContainer}>
38
+ <BackgroundHoursContent hours={hours} />
39
+ {extraRender}
40
+ {layout.partDayEventsLayout.map((partDayLayout) => (
41
+ <TimedEventContainer
42
+ key={partDayLayout.event.id}
43
+ layout={partDayLayout}
44
+ refNewEvent={refNewEvent}
45
+ />
46
+ ))}
47
+ {showTimeIndicator ? <TimeIndicator /> : null}
48
+ {canEditEvent ? <EditEventContainer refNewEvent={refNewEvent} /> : null}
49
+ </View>
50
+ {canCreateEvents ? <NewEventContainer /> : null}
51
+ </View>
52
+ );
53
+ };
54
+
55
+ export default TimedEvents;
56
+
57
+ const styles = StyleSheet.create({
58
+ container: {
59
+ flex: 1,
60
+ flexDirection: "row",
61
+ marginBottom: 30,
62
+ marginTop: 10,
63
+ },
64
+ backgroundContainer: {
65
+ position: "relative",
66
+ marginTop: -TOP_MARGIN_PIXEL_OFFSET,
67
+ flexDirection: "column",
68
+ flex: 1,
69
+ paddingRight: 10,
70
+ overflow: "hidden",
71
+ },
72
+ });
@@ -0,0 +1,146 @@
1
+ import Animated, {
2
+ runOnJS,
3
+ useAnimatedReaction,
4
+ useSharedValue,
5
+ } from "react-native-reanimated";
6
+ import { Gesture, GestureDetector } from "react-native-gesture-handler";
7
+ import { forwardRef, useContext, useEffect } from "react";
8
+ import { ConfigProvider, TOP_MARGIN_PIXEL_OFFSET } from "src/utils/globals";
9
+ import { StyleSheet } from "react-native";
10
+ import { GestureRef } from "react-native-gesture-handler/lib/typescript/handlers/gestures/gesture";
11
+ import { useIsEditing } from "src/hooks/use-is-editing";
12
+ import doubleTapGesture from "src/utils/double-tap-reset-zoom-gesture";
13
+
14
+ type ZoomProviderProps = {
15
+ children: any;
16
+ };
17
+
18
+ // This fraction determines how quickly zoom grows
19
+ const fraction = 0.1;
20
+
21
+ const ZoomProvider = forwardRef<GestureRef, ZoomProviderProps>(
22
+ ({ children }, refNewEvent) => {
23
+ const {
24
+ canCreateEvents,
25
+ zoomLevel,
26
+ initialZoomLevel,
27
+ createY,
28
+ onCreateEvent,
29
+ maximumHour,
30
+ onZoomChange,
31
+ } = useContext(ConfigProvider);
32
+ const previewScale = useSharedValue(-1);
33
+
34
+ useEffect(() => {
35
+ previewScale.value = zoomLevel.get();
36
+ }, [zoomLevel, previewScale]);
37
+
38
+ const pinchGesture = Gesture.Pinch()
39
+ .onUpdate((event) => {
40
+ "worklet";
41
+
42
+ const newScale =
43
+ previewScale.value * (1 + fraction * (event.scale - 1));
44
+
45
+ zoomLevel.value = Math.min(3, Math.max(0.54, newScale));
46
+ previewScale.value = zoomLevel.value;
47
+ })
48
+ .onEnd(() => {
49
+ if (onZoomChange) {
50
+ runOnJS(onZoomChange)(zoomLevel.value);
51
+ }
52
+ });
53
+
54
+ const yPosition = useSharedValue(-1);
55
+ const { isEditing } = useIsEditing();
56
+ const isDragging = useSharedValue(false);
57
+
58
+ useAnimatedReaction(
59
+ () => zoomLevel.value,
60
+ (zoom) => {
61
+ maximumHour.value = 1440 * zoom;
62
+ },
63
+ [maximumHour]
64
+ );
65
+
66
+ const longPressGesture = Gesture.LongPress()
67
+ .enabled(canCreateEvents && !isEditing)
68
+ .withRef(refNewEvent as any)
69
+ .numberOfPointers(1)
70
+ .minDuration(250)
71
+ .maxDistance(10000)
72
+ .onStart((event) => {
73
+ "worklet";
74
+
75
+ isDragging.value = true;
76
+ createY.value = Math.max(
77
+ 0,
78
+ event.y - TOP_MARGIN_PIXEL_OFFSET - (zoomLevel.value * 60) / 2
79
+ );
80
+ })
81
+ .onTouchesMove((event) => {
82
+ "worklet";
83
+
84
+ if (!isDragging.value) {
85
+ return;
86
+ }
87
+
88
+ createY.value = Math.max(
89
+ 0,
90
+ event.allTouches[0].y -
91
+ TOP_MARGIN_PIXEL_OFFSET -
92
+ (zoomLevel.value * 60) / 2
93
+ );
94
+ })
95
+ .onEnd((event, success) => {
96
+ "worklet";
97
+
98
+ if (!isDragging.value) {
99
+ return;
100
+ }
101
+
102
+ // Make sure it doesn't show the new event component anymore
103
+ createY.value = -1;
104
+ yPosition.value = -1;
105
+ isDragging.value = false;
106
+
107
+ if (!success) {
108
+ return;
109
+ }
110
+
111
+ // Determine the hour that was clicked and trigger the event creation
112
+ const normalizedY =
113
+ event.y - TOP_MARGIN_PIXEL_OFFSET - (zoomLevel.value * 60) / 2;
114
+ const time = Math.floor(normalizedY / zoomLevel.value);
115
+ const hour = Math.floor(time / 60);
116
+ const minute = time - hour * 60;
117
+
118
+ if (onCreateEvent) {
119
+ runOnJS(onCreateEvent)({
120
+ hour,
121
+ minute,
122
+ });
123
+ }
124
+ });
125
+
126
+ const combinedGesture = Gesture.Simultaneous(
127
+ pinchGesture,
128
+ longPressGesture,
129
+ doubleTapGesture(zoomLevel, initialZoomLevel, onZoomChange)
130
+ );
131
+
132
+ return (
133
+ <GestureDetector gesture={combinedGesture}>
134
+ <Animated.View style={styles.container}>{children}</Animated.View>
135
+ </GestureDetector>
136
+ );
137
+ }
138
+ );
139
+
140
+ const styles = StyleSheet.create({
141
+ container: {
142
+ flex: 1,
143
+ },
144
+ });
145
+
146
+ export default ZoomProvider;
package/src/enums.ts ADDED
File without changes
@@ -0,0 +1,26 @@
1
+ import { Dispatch, SetStateAction, useEffect, useState } from "react";
2
+ import { cloneDeep } from "lodash";
3
+ import { CalendarEvent } from "src/types";
4
+
5
+ export let updateClonedEvents: Dispatch<SetStateAction<CalendarEvent[]>>;
6
+
7
+ /**
8
+ * Returns a cloned version of the events array if the updateLocalStateAfterEdit is set to true.
9
+ * We do this to make local state updates faster and more responsive. If the updateLocalStateAfterEdit is set to false,
10
+ * we return the original events array and don't clone it.
11
+ */
12
+ const useClonedEvents = (
13
+ events: CalendarEvent[],
14
+ updateLocalStateAfterEdit: boolean
15
+ ) => {
16
+ const [clonedEvents, setClonedEvents] = useState(events);
17
+
18
+ useEffect(() => {
19
+ setClonedEvents(updateLocalStateAfterEdit ? cloneDeep(events) : events);
20
+ updateClonedEvents = setClonedEvents;
21
+ }, [events, updateLocalStateAfterEdit]);
22
+
23
+ return clonedEvents;
24
+ };
25
+
26
+ export default useClonedEvents;
@@ -0,0 +1,55 @@
1
+ import { create } from "zustand";
2
+ import { immer } from "zustand/middleware/immer";
3
+ import generateEventLayouts from "../utils/generate-event-layouts";
4
+ import { useEffect } from "react";
5
+ import {
6
+ CalendarEvent,
7
+ CalendarViewIntervalType,
8
+ FullCalendarEventLayout,
9
+ } from "src/types";
10
+
11
+ type State = {
12
+ events: FullCalendarEventLayout;
13
+ };
14
+
15
+ type Actions = {
16
+ updateEvents: (props: UpdateEvent) => void;
17
+ };
18
+
19
+ export type UpdateEvent = {
20
+ events: CalendarEvent[];
21
+ userCalendarId: string;
22
+ timezone: string;
23
+ startCalendarDate: string;
24
+ endCalendarDate: string;
25
+ startDayOfWeekOffset?: number;
26
+ calendarViewInterval?: CalendarViewIntervalType;
27
+ };
28
+
29
+ const useEventsLayoutStore = create<State & Actions>()(
30
+ immer((set) => ({
31
+ events: {
32
+ allDayEventsLayout: [],
33
+ partDayEventsLayout: [],
34
+ },
35
+ updateEvents: (props: UpdateEvent) =>
36
+ set((state) => {
37
+ state.events = generateEventLayouts(props)[props.startCalendarDate] || {
38
+ partDayEventsLayout: [],
39
+ allDayEventsLayout: [],
40
+ };
41
+ }),
42
+ }))
43
+ );
44
+
45
+ const useEventsLayout = (props: UpdateEvent) => {
46
+ const { events: layoutEvents } = useEventsLayoutStore();
47
+
48
+ useEffect(() => {
49
+ useEventsLayoutStore.getState().updateEvents(props);
50
+ }, [props]);
51
+
52
+ return layoutEvents;
53
+ };
54
+
55
+ export default useEventsLayout;
@@ -0,0 +1,109 @@
1
+ import React, {
2
+ createContext,
3
+ ReactNode,
4
+ useCallback,
5
+ useContext,
6
+ useState,
7
+ } from "react";
8
+ import { SharedValue, useSharedValue } from "react-native-reanimated";
9
+ import { ConfigProvider } from "src/utils/globals";
10
+ import { isFunction } from "lodash";
11
+ import { updateClonedEvents } from "src/hooks/use-cloned-events";
12
+ import { EditStatus, PartDayEventLayoutType } from "src/types";
13
+
14
+ interface IsEditingType {
15
+ isEditing: null | PartDayEventLayoutType;
16
+ currentY: SharedValue<number>;
17
+ setIsEditing: (
18
+ newValue: PartDayEventLayoutType | null,
19
+ updatedTimes?: {
20
+ updatedStart: string;
21
+ updatedEnd: string;
22
+ }
23
+ ) => void;
24
+ }
25
+
26
+ const IsEditing = createContext<IsEditingType | undefined>(undefined);
27
+
28
+ export const useIsEditing = () => {
29
+ const context = useContext(IsEditing);
30
+
31
+ if (!context) {
32
+ throw new Error("useIsEditing must be used within a IsEditingProvider");
33
+ }
34
+ return context;
35
+ };
36
+
37
+ // Provider component
38
+ export const IsEditingProvider = ({ children }: { children: ReactNode }) => {
39
+ const { canEditEvent, onEventEdit, updateLocalStateAfterEdit } =
40
+ useContext(ConfigProvider);
41
+ const [isEditing, baseSetIsEditing] = useState<null | PartDayEventLayoutType>(
42
+ null
43
+ );
44
+ const currentY = useSharedValue(0);
45
+
46
+ const setIsEditing = useCallback(
47
+ (
48
+ newValue: PartDayEventLayoutType | null,
49
+ updatedTimes?: {
50
+ updatedStart: string;
51
+ updatedEnd: string;
52
+ }
53
+ ) => {
54
+ if (newValue) {
55
+ if (isEditing) {
56
+ // Refuse to start a new edit
57
+ return;
58
+ }
59
+
60
+ const canEditEventParsed = isFunction(canEditEvent)
61
+ ? canEditEvent(newValue.event)
62
+ : canEditEvent;
63
+
64
+ if (!canEditEventParsed) {
65
+ return;
66
+ }
67
+
68
+ onEventEdit?.({
69
+ event: newValue.event,
70
+ status: EditStatus.Start,
71
+ });
72
+ } else if (isEditing) {
73
+ if (updateLocalStateAfterEdit) {
74
+ updateClonedEvents((events) => {
75
+ if (!updatedTimes) {
76
+ // This means we removed the event
77
+ return events.filter((event) => event.id !== isEditing.event.id);
78
+ }
79
+
80
+ return events.map((event) =>
81
+ event.id === isEditing.event.id
82
+ ? {
83
+ ...event,
84
+ start: updatedTimes?.updatedStart,
85
+ end: updatedTimes?.updatedEnd,
86
+ }
87
+ : event
88
+ );
89
+ });
90
+ }
91
+
92
+ onEventEdit?.({
93
+ event: isEditing.event,
94
+ status: EditStatus.Finish,
95
+ updatedTimes,
96
+ });
97
+ }
98
+
99
+ baseSetIsEditing(newValue);
100
+ },
101
+ [canEditEvent, isEditing, onEventEdit, updateLocalStateAfterEdit]
102
+ );
103
+
104
+ return (
105
+ <IsEditing.Provider value={{ currentY, isEditing, setIsEditing }}>
106
+ {children}
107
+ </IsEditing.Provider>
108
+ );
109
+ };
package/src/index.tsx ADDED
@@ -0,0 +1,165 @@
1
+ import { StyleSheet, View } from "react-native";
2
+ import AllDayEvents from "src/components/all-day-events";
3
+ import { ScrollView } from "react-native-gesture-handler";
4
+ import { useSharedValue } from "react-native-reanimated";
5
+ import ZoomProvider from "src/components/zoom-provider";
6
+ import TimedEvents from "src/components/timed-events";
7
+ import useEventsLayout, { UpdateEvent } from "src/hooks/use-events-layout";
8
+ import { ConfigProvider, DEFAULT_MINUTE_HEIGHT } from "src/utils/globals";
9
+ import moment from "moment-timezone";
10
+ import { useMemo, useRef } from "react";
11
+ import { GestureRef } from "react-native-gesture-handler/lib/typescript/handlers/gestures/gesture";
12
+ import { IsEditingProvider } from "src/hooks/use-is-editing";
13
+ import useClonedEvents from "src/hooks/use-cloned-events";
14
+ import type {
15
+ CalendarEvent,
16
+ Config,
17
+ onCreateEvent,
18
+ ThemeStyle,
19
+ } from "src/types";
20
+
21
+ type EventCalenderProps = {
22
+ canCreateEvents?: boolean;
23
+ canEditEvent?: Config["canEditEvent"];
24
+ dayDate: string;
25
+ events: CalendarEvent[];
26
+ fiveMinuteInterval?: boolean;
27
+ initialZoomLevel?: number;
28
+ maxAllDayEvents?: number;
29
+ onCreateEvent?: onCreateEvent;
30
+ onEventEdit?: Config["onEventEdit"];
31
+ onPressEvent?: Config["onPressEvent"];
32
+ renderDragBars?: Config["renderDragBars"];
33
+ renderEvent: Config["renderEvent"];
34
+ renderNewEventContainer?: Config["renderNewEventContainer"];
35
+ showTimeIndicator?: boolean;
36
+ theme?: ThemeStyle;
37
+ timeFormat?: string;
38
+ timezone?: string;
39
+ updateLocalStateAfterEdit?: boolean;
40
+ userCalendarId?: string;
41
+ extraTimedComponents?: Config["extraTimedComponents"];
42
+ onZoomChange?: Config["onZoomChange"];
43
+ };
44
+
45
+ const EventCalendar = ({
46
+ events,
47
+ timeFormat = "HH:mm",
48
+ dayDate,
49
+ theme,
50
+ initialZoomLevel = DEFAULT_MINUTE_HEIGHT,
51
+ onCreateEvent,
52
+ timezone = "UTC",
53
+ renderEvent,
54
+ onPressEvent,
55
+ userCalendarId = "",
56
+ showTimeIndicator,
57
+ maxAllDayEvents = 2,
58
+ canCreateEvents = true,
59
+ renderNewEventContainer,
60
+ fiveMinuteInterval,
61
+ canEditEvent = true,
62
+ onEventEdit,
63
+ renderDragBars,
64
+ updateLocalStateAfterEdit = true,
65
+ extraTimedComponents,
66
+ onZoomChange,
67
+ }: EventCalenderProps) => {
68
+ const startCalendarDate = useMemo(
69
+ () => moment.tz(dayDate, timezone).startOf("day"),
70
+ [dayDate, timezone]
71
+ );
72
+
73
+ const clonedEvents = useClonedEvents(events, updateLocalStateAfterEdit);
74
+
75
+ const memoizedProps = useMemo<UpdateEvent>(
76
+ () => ({
77
+ startCalendarDate: startCalendarDate.format("YYYY-MM-DD"),
78
+ calendarViewInterval: "1day",
79
+ endCalendarDate: startCalendarDate.format("YYYY-MM-DD"),
80
+ userCalendarId,
81
+ timezone,
82
+ startDayOfWeekOffset: 0,
83
+ events: clonedEvents,
84
+ }),
85
+ [startCalendarDate, userCalendarId, timezone, clonedEvents]
86
+ );
87
+
88
+ const layout = useEventsLayout(memoizedProps);
89
+ const zoomLevel = useSharedValue(initialZoomLevel);
90
+ const createY = useSharedValue(-1);
91
+ const maximumHour = useSharedValue(0);
92
+
93
+ const refNewEvent = useRef<GestureRef>(null);
94
+
95
+ return (
96
+ <View style={[styles.container, theme?.container]}>
97
+ <ConfigProvider.Provider
98
+ value={{
99
+ dayDate: startCalendarDate,
100
+ timeFormat,
101
+ layout,
102
+ zoomLevel,
103
+ createY,
104
+ initialZoomLevel,
105
+ onCreateEvent,
106
+ timezone,
107
+ renderEvent,
108
+ onPressEvent,
109
+ showTimeIndicator,
110
+ maxAllDayEvents,
111
+ canCreateEvents,
112
+ renderNewEventContainer,
113
+ fiveMinuteInterval,
114
+ canEditEvent,
115
+ onEventEdit,
116
+ renderDragBars,
117
+ maximumHour,
118
+ updateLocalStateAfterEdit,
119
+ extraTimedComponents,
120
+ onZoomChange,
121
+ }}
122
+ >
123
+ <AllDayEvents />
124
+ <ScrollView
125
+ bounces={false}
126
+ keyboardShouldPersistTaps="always"
127
+ style={[styles.scrollView, theme?.scrollView]}
128
+ >
129
+ <IsEditingProvider>
130
+ <ZoomProvider ref={refNewEvent}>
131
+ <View style={[styles.borderContainer, theme?.borderContainer]} />
132
+ <TimedEvents refNewEvent={refNewEvent} />
133
+ </ZoomProvider>
134
+ </IsEditingProvider>
135
+ </ScrollView>
136
+ </ConfigProvider.Provider>
137
+ </View>
138
+ );
139
+ };
140
+
141
+ const styles = StyleSheet.create({
142
+ container: {
143
+ flex: 1,
144
+ marginTop: 8,
145
+ borderTopWidth: StyleSheet.hairlineWidth,
146
+ borderTopColor: "#F0F0F0",
147
+ overflow: "hidden",
148
+ },
149
+ scrollView: {
150
+ paddingTop: 8,
151
+ backgroundColor: "white",
152
+ flex: 1,
153
+ },
154
+ borderContainer: {
155
+ position: "absolute",
156
+ height: "200%",
157
+ left: 50,
158
+ top: -18,
159
+ width: 5,
160
+ borderLeftWidth: StyleSheet.hairlineWidth,
161
+ borderColor: "black",
162
+ },
163
+ });
164
+
165
+ export default EventCalendar;