react-native-ll-calendar 0.9.1 → 0.11.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 (26) hide show
  1. package/lib/module/calendar/resources-calendar/ResourcesCalendar.js +429 -0
  2. package/lib/module/calendar/resources-calendar/ResourcesCalendar.js.map +1 -0
  3. package/lib/module/index.js +1 -0
  4. package/lib/module/index.js.map +1 -1
  5. package/lib/module/types/resources-calendar.js +2 -0
  6. package/lib/module/types/resources-calendar.js.map +1 -0
  7. package/lib/module/utils/functions.js +30 -0
  8. package/lib/module/utils/functions.js.map +1 -1
  9. package/lib/module/utils/resources-calendar-event-position.js +68 -0
  10. package/lib/module/utils/resources-calendar-event-position.js.map +1 -0
  11. package/lib/typescript/src/calendar/resources-calendar/ResourcesCalendar.d.ts +34 -0
  12. package/lib/typescript/src/calendar/resources-calendar/ResourcesCalendar.d.ts.map +1 -0
  13. package/lib/typescript/src/index.d.ts +3 -0
  14. package/lib/typescript/src/index.d.ts.map +1 -1
  15. package/lib/typescript/src/types/resources-calendar.d.ts +18 -0
  16. package/lib/typescript/src/types/resources-calendar.d.ts.map +1 -0
  17. package/lib/typescript/src/utils/functions.d.ts +8 -0
  18. package/lib/typescript/src/utils/functions.d.ts.map +1 -1
  19. package/lib/typescript/src/utils/resources-calendar-event-position.d.ts +22 -0
  20. package/lib/typescript/src/utils/resources-calendar-event-position.d.ts.map +1 -0
  21. package/package.json +1 -1
  22. package/src/calendar/resources-calendar/ResourcesCalendar.tsx +580 -0
  23. package/src/index.tsx +7 -0
  24. package/src/types/resources-calendar.ts +18 -0
  25. package/src/utils/functions.ts +34 -0
  26. package/src/utils/resources-calendar-event-position.ts +68 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AACxE,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AACxE,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,YAAY,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAEhF,OAAO,EAAE,iBAAiB,EAAE,MAAM,iDAAiD,CAAC;AACpF,YAAY,EACV,aAAa,IAAI,sBAAsB,EACvC,gBAAgB,GACjB,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1,18 @@
1
+ export type CalendarEvent = {
2
+ id: string;
3
+ resourceId: string;
4
+ title: string;
5
+ start: Date;
6
+ end: Date;
7
+ backgroundColor: string;
8
+ borderColor: string;
9
+ color: string;
10
+ borderStyle?: 'solid' | 'dashed' | 'dotted';
11
+ borderWidth?: number;
12
+ borderRadius?: number;
13
+ };
14
+ export type CalendarResource = {
15
+ id: string;
16
+ name: string;
17
+ };
18
+ //# sourceMappingURL=resources-calendar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources-calendar.d.ts","sourceRoot":"","sources":["../../../../src/types/resources-calendar.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,IAAI,CAAC;IACZ,GAAG,EAAE,IAAI,CAAC;IACV,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd,CAAC"}
@@ -12,4 +12,12 @@ export declare function getWeekIds(args: {
12
12
  end: Date;
13
13
  weekStartsOn: WeekStartsOn;
14
14
  }): string[];
15
+ export declare function generateDates(from: Date, to: Date): Date[];
16
+ type MonthGroup = {
17
+ year: number;
18
+ month: number;
19
+ dates: Date[];
20
+ };
21
+ export declare function groupDatesByMonth(dates: Date[]): MonthGroup[];
22
+ export {};
15
23
  //# sourceMappingURL=functions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../../../../src/utils/functions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,IAAI,EAAE,IAAI,CAAC;IACX,YAAY,EAAE,YAAY,CAAC;CAC5B,QAeA;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,IAAI,EAAE,IAAI,CAAC;IACX,YAAY,EAAE,YAAY,CAAC;CAC5B,QAaA;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE;IAC/B,KAAK,EAAE,IAAI,CAAC;IACZ,GAAG,EAAE,IAAI,CAAC;IACV,YAAY,EAAE,YAAY,CAAC;CAC5B,YAiCA"}
1
+ {"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../../../../src/utils/functions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,IAAI,EAAE,IAAI,CAAC;IACX,YAAY,EAAE,YAAY,CAAC;CAC5B,QAeA;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,IAAI,EAAE,IAAI,CAAC;IACX,YAAY,EAAE,YAAY,CAAC;CAC5B,QAaA;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE;IAC/B,KAAK,EAAE,IAAI,CAAC;IACZ,GAAG,EAAE,IAAI,CAAC;IACV,YAAY,EAAE,YAAY,CAAC;CAC5B,YAiCA;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,GAAG,IAAI,EAAE,CAW1D;AAED,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,IAAI,EAAE,CAAC;CACf,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,UAAU,EAAE,CAa7D"}
@@ -0,0 +1,22 @@
1
+ declare class ResourcesCalendarEventPosition {
2
+ record: Record<string, Record<string, number[]>>;
3
+ constructor();
4
+ private generateKey;
5
+ push(arg: {
6
+ resourceId: string;
7
+ startDate: Date;
8
+ days: number;
9
+ rowNum: number;
10
+ }): void;
11
+ getMaxRowNum(arg: {
12
+ resourceId: string;
13
+ date: Date;
14
+ }): number;
15
+ getRowNums(arg: {
16
+ resourceId: string;
17
+ date: Date;
18
+ }): number[];
19
+ reset(resourceId: string): void;
20
+ }
21
+ export default ResourcesCalendarEventPosition;
22
+ //# sourceMappingURL=resources-calendar-event-position.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources-calendar-event-position.d.ts","sourceRoot":"","sources":["../../../../src/utils/resources-calendar-event-position.ts"],"names":[],"mappings":"AAAA,cAAM,8BAA8B;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAM;;IAG7D,OAAO,CAAC,WAAW;IAOZ,IAAI,CAAC,GAAG,EAAE;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,IAAI,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;KAChB;IAiBM,YAAY,CAAC,GAAG,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,GAAG,MAAM;IAiB7D,UAAU,CAAC,GAAG,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,GAAG,MAAM,EAAE;IAa7D,KAAK,CAAC,UAAU,EAAE,MAAM;CAGhC;AACD,eAAe,8BAA8B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-ll-calendar",
3
- "version": "0.9.1",
3
+ "version": "0.11.0",
4
4
  "description": "ReactNative Calendar Library",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -0,0 +1,580 @@
1
+ import React, { useCallback, useMemo, useRef, useState } from 'react';
2
+ import type {
3
+ CalendarEvent,
4
+ CalendarResource,
5
+ } from '../../types/resources-calendar';
6
+ import {
7
+ type NativeScrollEvent,
8
+ type NativeSyntheticEvent,
9
+ type TextStyle,
10
+ type ViewStyle,
11
+ RefreshControl,
12
+ ScrollView,
13
+ StyleSheet,
14
+ Text,
15
+ TouchableOpacity,
16
+ } from 'react-native';
17
+ import { View } from 'react-native';
18
+ import { generateDates, groupDatesByMonth } from '../../utils/functions';
19
+ import dayjs from 'dayjs';
20
+ import ResourcesCalendarEventPosition from '../../utils/resources-calendar-event-position';
21
+ import { EVENT_GAP } from '../../constants/size';
22
+
23
+ type ResourcesCalendarProps = {
24
+ fromDate: Date;
25
+ toDate: Date;
26
+ resources: CalendarResource[];
27
+ events: CalendarEvent[];
28
+ renderDateLabel?: (date: Date) => React.JSX.Element;
29
+ renderMonthLabel?: (year: number, month: number) => React.JSX.Element;
30
+ renderResourceNameLabel?: (resource: CalendarResource) => React.JSX.Element;
31
+ resourceColumnWidth?: number;
32
+ dateColumnWidth?: number;
33
+ onRefresh?: () => void;
34
+ refreshing?: boolean;
35
+ fixedRowCount?: number;
36
+ onPressCell?: (resource: CalendarResource, date: Date) => void;
37
+ onLongPressCell?: (resource: CalendarResource, date: Date) => void;
38
+ delayLongPressCell?: number;
39
+ onPressEvent?: (event: CalendarEvent) => void;
40
+ onLongPressEvent?: (event: CalendarEvent) => void;
41
+ delayLongPressEvent?: number;
42
+ eventHeight?: number;
43
+ bottomSpacing?: number;
44
+ eventTextStyle?: (event: CalendarEvent) => TextStyle;
45
+ eventEllipsizeMode?: 'head' | 'middle' | 'tail' | 'clip';
46
+ dateCellContainerStyle?: (date: Date) => ViewStyle;
47
+ cellContainerStyle?: (resource: CalendarResource, date: Date) => ViewStyle;
48
+ hiddenMonth?: boolean;
49
+ allowFontScaling?: boolean;
50
+ };
51
+
52
+ const DEFAULT_DATE_COLUMN_WIDTH = 60;
53
+ const DEFAULT_EVENT_HEIGHT = 22;
54
+ const CELL_BORDER_WIDTH = 0.5;
55
+
56
+ type ResourceRowProps = {
57
+ resource: CalendarResource;
58
+ dates: Date[];
59
+ dateColumnWidth: number;
60
+ scrollOffset: number;
61
+ eventsByResourceId: Map<string, CalendarEvent[]>;
62
+ renderResourceNameLabel?: (resource: CalendarResource) => React.JSX.Element;
63
+ onPressCell?: (resource: CalendarResource, date: Date) => void;
64
+ onLongPressCell?: (resource: CalendarResource, date: Date) => void;
65
+ delayLongPressCell?: number;
66
+ onPressEvent?: (event: CalendarEvent) => void;
67
+ onLongPressEvent?: (event: CalendarEvent) => void;
68
+ delayLongPressEvent?: number;
69
+ eventHeight: number;
70
+ eventTextStyle?: (event: CalendarEvent) => TextStyle;
71
+ eventEllipsizeMode?: 'head' | 'middle' | 'tail' | 'clip';
72
+ cellContainerStyle?: (resource: CalendarResource, date: Date) => ViewStyle;
73
+ allowFontScaling?: boolean;
74
+ };
75
+
76
+ function ResourceRow({
77
+ resource,
78
+ dates,
79
+ dateColumnWidth,
80
+ scrollOffset,
81
+ eventsByResourceId,
82
+ renderResourceNameLabel,
83
+ onPressCell,
84
+ onLongPressCell,
85
+ delayLongPressCell,
86
+ onPressEvent,
87
+ onLongPressEvent,
88
+ delayLongPressEvent,
89
+ eventHeight,
90
+ eventTextStyle,
91
+ eventEllipsizeMode,
92
+ cellContainerStyle,
93
+ allowFontScaling,
94
+ }: ResourceRowProps) {
95
+ const resourceEvents = eventsByResourceId.get(resource.id) ?? [];
96
+ const eventPosition = new ResourcesCalendarEventPosition();
97
+
98
+ return (
99
+ <View style={styles.resourceRow}>
100
+ <View style={[styles.resourceNameFixedLabel]}>
101
+ <View style={{ marginLeft: scrollOffset + 4 }}>
102
+ {renderResourceNameLabel ? (
103
+ renderResourceNameLabel(resource)
104
+ ) : (
105
+ <View>
106
+ <Text
107
+ allowFontScaling={allowFontScaling}
108
+ style={styles.resourceNameFixedLabelText}
109
+ >
110
+ {resource.name}
111
+ </Text>
112
+ </View>
113
+ )}
114
+ </View>
115
+ </View>
116
+ <View style={styles.resourceRowContentArea}>
117
+ {dates.map((date, dateIndex) => {
118
+ const djs = dayjs(date);
119
+
120
+ // この日付が開始日のイベント、または行の先頭で前日から続くイベントを抽出
121
+ const filteredEvents = resourceEvents
122
+ .filter((event) => {
123
+ const startDjs = dayjs(event.start);
124
+ return (
125
+ startDjs.format('YYYY-MM-DD') === djs.format('YYYY-MM-DD') ||
126
+ (dateIndex === 0 && startDjs.isBefore(djs))
127
+ );
128
+ })
129
+ .sort((a, b) => {
130
+ const aStartDjs = dateIndex === 0 ? djs : dayjs(a.start);
131
+ const bStartDjs = dateIndex === 0 ? djs : dayjs(b.start);
132
+ const aDiffDays = dayjs(a.end)
133
+ .startOf('day')
134
+ .diff(aStartDjs.startOf('day'), 'day');
135
+ const bDiffDays = dayjs(b.end)
136
+ .startOf('day')
137
+ .diff(bStartDjs.startOf('day'), 'day');
138
+ if (aDiffDays !== bDiffDays) {
139
+ return bDiffDays - aDiffDays;
140
+ }
141
+ return dayjs(a.start).diff(dayjs(b.start));
142
+ });
143
+
144
+ // 行番号を考慮してイベントを配置(重複回避)
145
+ const rowNums = eventPosition.getRowNums({
146
+ resourceId: resource.id,
147
+ date,
148
+ });
149
+ const cellEvents: (CalendarEvent | number)[] = [];
150
+ const rowsLength = rowNums.length + filteredEvents.length;
151
+ let eventIndex = 0;
152
+ for (let ii = 1; ii <= rowsLength; ii++) {
153
+ if (rowNums.includes(ii)) {
154
+ cellEvents.push(ii);
155
+ } else {
156
+ const event = filteredEvents[eventIndex];
157
+ if (event) {
158
+ cellEvents.push(event);
159
+ }
160
+ eventIndex++;
161
+ }
162
+ }
163
+
164
+ return (
165
+ <TouchableOpacity
166
+ key={date.getTime()}
167
+ style={[
168
+ styles.contentCellContainer,
169
+ { width: dateColumnWidth },
170
+ { zIndex: dates.length - dateIndex },
171
+ cellContainerStyle?.(resource, date),
172
+ ]}
173
+ onPress={() => onPressCell?.(resource, date)}
174
+ onLongPress={() => onLongPressCell?.(resource, date)}
175
+ delayLongPress={delayLongPressCell}
176
+ activeOpacity={1}
177
+ >
178
+ {cellEvents.map((event, rowIndex) => {
179
+ if (typeof event === 'number') {
180
+ return (
181
+ <View
182
+ key={event}
183
+ style={{
184
+ height: eventHeight,
185
+ marginBottom: EVENT_GAP,
186
+ }}
187
+ />
188
+ );
189
+ }
190
+
191
+ const rawStartDjs = dayjs(event.start);
192
+ const startDjs = dateIndex === 0 ? djs : dayjs(event.start);
193
+ const endDjs = dayjs(event.end);
194
+ const diffDays = endDjs
195
+ .startOf('day')
196
+ .diff(startDjs.startOf('day'), 'day');
197
+ const isPrevDateEvent =
198
+ dateIndex === 0 && rawStartDjs.isBefore(djs);
199
+
200
+ // イベントの幅を日数に応じて計算
201
+ let width =
202
+ (diffDays + 1) * dateColumnWidth -
203
+ EVENT_GAP * 2 -
204
+ CELL_BORDER_WIDTH * 2;
205
+ if (isPrevDateEvent) {
206
+ width += EVENT_GAP + 1;
207
+ }
208
+
209
+ // 位置情報を記録
210
+ eventPosition.push({
211
+ resourceId: resource.id,
212
+ startDate: startDjs.toDate(),
213
+ days: diffDays + 1,
214
+ rowNum: rowIndex + 1,
215
+ });
216
+
217
+ return (
218
+ <TouchableOpacity
219
+ data-component-name="resources-calendar-event"
220
+ key={event.id}
221
+ style={[
222
+ styles.event,
223
+ {
224
+ backgroundColor: event.backgroundColor,
225
+ borderColor: event.borderColor,
226
+ width,
227
+ height: eventHeight,
228
+ ...(event.borderStyle !== undefined && {
229
+ borderStyle: event.borderStyle,
230
+ }),
231
+ ...(event.borderWidth !== undefined && {
232
+ borderWidth: event.borderWidth,
233
+ }),
234
+ ...(event.borderRadius !== undefined && {
235
+ borderRadius: event.borderRadius,
236
+ }),
237
+ },
238
+ isPrevDateEvent ? styles.prevDateEvent : {},
239
+ ]}
240
+ onPress={() => onPressEvent?.(event)}
241
+ onLongPress={() => onLongPressEvent?.(event)}
242
+ delayLongPress={delayLongPressEvent}
243
+ >
244
+ <Text
245
+ numberOfLines={1}
246
+ ellipsizeMode={eventEllipsizeMode ?? 'tail'}
247
+ allowFontScaling={allowFontScaling}
248
+ style={[
249
+ styles.eventTitle,
250
+ { color: event.color },
251
+ eventTextStyle?.(event),
252
+ ]}
253
+ >
254
+ {event.title}
255
+ </Text>
256
+ </TouchableOpacity>
257
+ );
258
+ })}
259
+ </TouchableOpacity>
260
+ );
261
+ })}
262
+ </View>
263
+ </View>
264
+ );
265
+ }
266
+
267
+ type ScrollViewRef = React.ComponentRef<typeof ScrollView>;
268
+
269
+ export function ResourcesCalendar(props: ResourcesCalendarProps) {
270
+ const dateColumnWidth = props.dateColumnWidth ?? DEFAULT_DATE_COLUMN_WIDTH;
271
+ const fixedRowCount = props.fixedRowCount ?? 0;
272
+
273
+ const dates = useMemo(
274
+ () => generateDates(props.fromDate, props.toDate),
275
+ [props.fromDate, props.toDate]
276
+ );
277
+
278
+ const monthGroups = useMemo(() => groupDatesByMonth(dates), [dates]);
279
+
280
+ const eventsByResourceId = useMemo(() => {
281
+ const map = new Map<string, CalendarEvent[]>();
282
+ for (const event of props.events) {
283
+ const list = map.get(event.resourceId) ?? [];
284
+ list.push(event);
285
+ map.set(event.resourceId, list);
286
+ }
287
+ return map;
288
+ }, [props.events]);
289
+
290
+ const monthGroupOffsets = useMemo(() => {
291
+ let offset = 0;
292
+ return monthGroups.map((group) => {
293
+ const start = offset;
294
+ offset += group.dates.length * dateColumnWidth;
295
+ return { start, width: group.dates.length * dateColumnWidth };
296
+ });
297
+ }, [monthGroups, dateColumnWidth]);
298
+
299
+ const fixedResources = useMemo(
300
+ () => props.resources.slice(0, fixedRowCount),
301
+ [props.resources, fixedRowCount]
302
+ );
303
+ const scrollableResources = useMemo(
304
+ () => props.resources.slice(fixedRowCount),
305
+ [props.resources, fixedRowCount]
306
+ );
307
+
308
+ const headerScrollRef = useRef<ScrollViewRef>(null);
309
+ const bodyScrollRef = useRef<ScrollViewRef>(null);
310
+ const activeScroller = useRef<'header' | 'body' | null>(null);
311
+ const activeScrollerTimer = useRef<ReturnType<typeof setTimeout> | null>(
312
+ null
313
+ );
314
+ const lastScrollX = useRef(0);
315
+ const [scrollOffset, setScrollOffset] = useState(0);
316
+
317
+ // スクロール停止後にラベルを追従させる。
318
+ // activeScroller のタイムアウトが切れた = スクロール停止とみなし、
319
+ // その時点の lastScrollX を state に反映する。
320
+ const releaseActiveScroller = useCallback(() => {
321
+ activeScroller.current = null;
322
+ setScrollOffset(lastScrollX.current);
323
+ }, []);
324
+
325
+ const handleHeaderScroll = useCallback(
326
+ (event: NativeSyntheticEvent<NativeScrollEvent>) => {
327
+ if (activeScroller.current === 'body') {
328
+ return;
329
+ }
330
+ const x = event.nativeEvent.contentOffset.x;
331
+ lastScrollX.current = x;
332
+ activeScroller.current = 'header';
333
+
334
+ if (activeScrollerTimer.current != null) {
335
+ clearTimeout(activeScrollerTimer.current);
336
+ }
337
+ activeScrollerTimer.current = setTimeout(releaseActiveScroller, 150);
338
+
339
+ bodyScrollRef.current?.scrollTo({ x, animated: false });
340
+ },
341
+ [releaseActiveScroller]
342
+ );
343
+
344
+ const handleBodyScroll = useCallback(
345
+ (event: NativeSyntheticEvent<NativeScrollEvent>) => {
346
+ if (activeScroller.current === 'header') {
347
+ return;
348
+ }
349
+ const x = event.nativeEvent.contentOffset.x;
350
+ lastScrollX.current = x;
351
+ activeScroller.current = 'body';
352
+
353
+ if (activeScrollerTimer.current != null) {
354
+ clearTimeout(activeScrollerTimer.current);
355
+ }
356
+ activeScrollerTimer.current = setTimeout(releaseActiveScroller, 150);
357
+
358
+ headerScrollRef.current?.scrollTo({ x, animated: false });
359
+ },
360
+ [releaseActiveScroller]
361
+ );
362
+
363
+ const commonRowProps = {
364
+ dates,
365
+ dateColumnWidth,
366
+ scrollOffset,
367
+ eventsByResourceId,
368
+ renderResourceNameLabel: props.renderResourceNameLabel,
369
+ onPressCell: props.onPressCell,
370
+ onLongPressCell: props.onLongPressCell,
371
+ delayLongPressCell: props.delayLongPressCell,
372
+ onPressEvent: props.onPressEvent,
373
+ onLongPressEvent: props.onLongPressEvent,
374
+ delayLongPressEvent: props.delayLongPressEvent,
375
+ eventHeight: props.eventHeight ?? DEFAULT_EVENT_HEIGHT,
376
+ eventTextStyle: props.eventTextStyle,
377
+ eventEllipsizeMode: props.eventEllipsizeMode,
378
+ cellContainerStyle: props.cellContainerStyle,
379
+ allowFontScaling: props.allowFontScaling,
380
+ };
381
+
382
+ return (
383
+ <ScrollView
384
+ stickyHeaderIndices={[0]}
385
+ refreshControl={
386
+ <RefreshControl
387
+ refreshing={!!props.refreshing}
388
+ onRefresh={props.onRefresh}
389
+ />
390
+ }
391
+ >
392
+ <ScrollView
393
+ ref={headerScrollRef}
394
+ horizontal
395
+ showsHorizontalScrollIndicator={false}
396
+ bounces={false}
397
+ overScrollMode="never"
398
+ onScroll={handleHeaderScroll}
399
+ scrollEventThrottle={16}
400
+ data-component-name="resources-calendar-header-row"
401
+ >
402
+ <View>
403
+ {!props.hiddenMonth && (
404
+ <View style={styles.monthHeaderRow}>
405
+ {monthGroups.map(({ year, month }, index) => {
406
+ const { start: cellStart, width: cellWidth } =
407
+ monthGroupOffsets[index]!;
408
+ const textLeft = Math.max(8, scrollOffset - cellStart + 8);
409
+ return (
410
+ <View
411
+ key={`${year}-${month}`}
412
+ style={[styles.monthHeaderCell, { width: cellWidth }]}
413
+ >
414
+ <View style={{ marginLeft: textLeft }}>
415
+ {props.renderMonthLabel ? (
416
+ props.renderMonthLabel(year, month)
417
+ ) : (
418
+ <View>
419
+ <Text
420
+ numberOfLines={1}
421
+ allowFontScaling={props.allowFontScaling}
422
+ style={styles.monthHeaderText}
423
+ >
424
+ {dayjs(`${year}-${month}-01`).format('YYYY/MM')}
425
+ </Text>
426
+ </View>
427
+ )}
428
+ </View>
429
+ </View>
430
+ );
431
+ })}
432
+ </View>
433
+ )}
434
+
435
+ <View style={styles.headerRow}>
436
+ {dates.map((date) => (
437
+ <View
438
+ key={date.getTime()}
439
+ data-component-name="resources-calendar-date-cell"
440
+ style={[
441
+ styles.dateCellContainer,
442
+ { width: dateColumnWidth },
443
+ props.dateCellContainerStyle?.(date),
444
+ ]}
445
+ >
446
+ {props.renderDateLabel ? (
447
+ props.renderDateLabel(date)
448
+ ) : (
449
+ <View>
450
+ <Text allowFontScaling={props.allowFontScaling}>
451
+ {dayjs(date).format('D(ddd)')}
452
+ </Text>
453
+ </View>
454
+ )}
455
+ </View>
456
+ ))}
457
+ </View>
458
+
459
+ <View>
460
+ {fixedResources.map((resource) => (
461
+ <ResourceRow
462
+ key={resource.id}
463
+ resource={resource}
464
+ {...commonRowProps}
465
+ />
466
+ ))}
467
+ </View>
468
+ </View>
469
+ </ScrollView>
470
+
471
+ <ScrollView
472
+ ref={bodyScrollRef}
473
+ horizontal
474
+ showsHorizontalScrollIndicator={false}
475
+ bounces={false}
476
+ overScrollMode="never"
477
+ onScroll={handleBodyScroll}
478
+ scrollEventThrottle={16}
479
+ data-component-name="resources-calendar-body-row"
480
+ >
481
+ <View>
482
+ {scrollableResources.map((resource) => (
483
+ <ResourceRow
484
+ key={resource.id}
485
+ resource={resource}
486
+ {...commonRowProps}
487
+ />
488
+ ))}
489
+ </View>
490
+ </ScrollView>
491
+ <View style={{ height: props.bottomSpacing }} />
492
+ </ScrollView>
493
+ );
494
+ }
495
+
496
+ const styles = StyleSheet.create({
497
+ monthHeaderRow: {
498
+ flexDirection: 'row',
499
+ backgroundColor: 'white',
500
+ },
501
+ monthHeaderCell: {
502
+ justifyContent: 'center',
503
+ alignItems: 'flex-start',
504
+ overflow: 'hidden',
505
+ borderRightWidth: CELL_BORDER_WIDTH,
506
+ borderRightColor: 'lightslategrey',
507
+ borderTopWidth: CELL_BORDER_WIDTH,
508
+ borderTopColor: 'lightslategrey',
509
+ height: 18,
510
+ },
511
+ monthHeaderText: {
512
+ fontSize: 12,
513
+ color: '#333',
514
+ },
515
+ headerRow: {
516
+ flexDirection: 'row',
517
+ borderBottomWidth: CELL_BORDER_WIDTH,
518
+ borderRightWidth: CELL_BORDER_WIDTH,
519
+ borderBottomColor: 'lightslategrey',
520
+ backgroundColor: 'white',
521
+ },
522
+ dateCellContainer: {
523
+ width: 60,
524
+ borderTopWidth: CELL_BORDER_WIDTH,
525
+ borderRightWidth: CELL_BORDER_WIDTH,
526
+ borderColor: 'lightslategrey',
527
+ },
528
+ resourceNameCellContainer: {
529
+ width: 80,
530
+ borderRightWidth: CELL_BORDER_WIDTH,
531
+ borderColor: 'lightslategrey',
532
+ },
533
+ resourceRow: {
534
+ flexDirection: 'column',
535
+ borderBottomWidth: CELL_BORDER_WIDTH,
536
+ borderRightWidth: CELL_BORDER_WIDTH,
537
+ borderBottomColor: 'lightslategrey',
538
+ backgroundColor: 'white',
539
+ },
540
+ resourceRowContentArea: {
541
+ flexDirection: 'row',
542
+ minHeight: 30,
543
+ },
544
+ contentCellContainer: {
545
+ paddingBottom: EVENT_GAP,
546
+ width: 60,
547
+ borderRightWidth: CELL_BORDER_WIDTH,
548
+ borderColor: 'lightslategrey',
549
+ },
550
+ resourceNameColumn: {
551
+ width: 80,
552
+ },
553
+ resourceNameFixedLabel: {
554
+ width: '100%',
555
+ borderBottomWidth: CELL_BORDER_WIDTH,
556
+ borderColor: 'lightslategrey',
557
+ backgroundColor: '#EEEEEE',
558
+ },
559
+ resourceNameFixedLabelText: {
560
+ fontSize: 12,
561
+ color: 'black',
562
+ },
563
+ event: {
564
+ borderWidth: 0.5,
565
+ borderRadius: 4,
566
+ paddingHorizontal: 4,
567
+ flexDirection: 'row',
568
+ alignItems: 'center',
569
+ marginTop: EVENT_GAP,
570
+ marginLeft: EVENT_GAP,
571
+ },
572
+ prevDateEvent: {
573
+ marginLeft: -1,
574
+ borderTopStartRadius: 0,
575
+ borderBottomStartRadius: 0,
576
+ },
577
+ eventTitle: {
578
+ fontSize: 12,
579
+ },
580
+ });
package/src/index.tsx CHANGED
@@ -1,2 +1,9 @@
1
1
  export { MonthCalendar } from './calendar/month-calendar/MonthCalendar';
2
2
  export type { CalendarEvent } from './types/month-calendar';
3
+ export type { MonthCalendarRef } from './calendar/month-calendar/MonthCalendar';
4
+
5
+ export { ResourcesCalendar } from './calendar/resources-calendar/ResourcesCalendar';
6
+ export type {
7
+ CalendarEvent as ResourcesCalendarEvent,
8
+ CalendarResource,
9
+ } from './types/resources-calendar';
@@ -0,0 +1,18 @@
1
+ export type CalendarEvent = {
2
+ id: string;
3
+ resourceId: string;
4
+ title: string;
5
+ start: Date;
6
+ end: Date;
7
+ backgroundColor: string;
8
+ borderColor: string;
9
+ color: string;
10
+ borderStyle?: 'solid' | 'dashed' | 'dotted';
11
+ borderWidth?: number;
12
+ borderRadius?: number;
13
+ };
14
+
15
+ export type CalendarResource = {
16
+ id: string;
17
+ name: string;
18
+ };