@super-calendar/native 2.0.0 → 2.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.
@@ -1,3 +1,4 @@
1
+ import type { ReactElement } from "react";
1
2
  import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
2
3
  import { useCalendarTheme } from "../theme";
3
4
  import type { RenderEventArgs } from "../types";
@@ -26,7 +27,7 @@ export function DefaultMonthEvent<T>({
26
27
  cellStyle,
27
28
  onPress,
28
29
  onLongPress,
29
- }: RenderEventArgs<T>) {
30
+ }: RenderEventArgs<T>): ReactElement {
30
31
  const theme = useCalendarTheme();
31
32
  const isAllDayEvent = isAllDay ?? false;
32
33
  const timeLabel = eventTimeLabel({
@@ -12,7 +12,7 @@ import {
12
12
  type Locale,
13
13
  startOfMonth,
14
14
  } from "date-fns";
15
- import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
15
+ import { memo, type ReactElement, useCallback, useEffect, useMemo, useRef, useState } from "react";
16
16
  import {
17
17
  type LayoutChangeEvent,
18
18
  type NativeScrollEvent,
@@ -69,6 +69,7 @@ const noop = () => {};
69
69
  const defaultKeyExtractor: EventKeyExtractor<unknown> = (event) =>
70
70
  `${event.start.toISOString()}|${event.end.toISOString()}|${event.title ?? ""}`;
71
71
 
72
+ /** Props for {@link MonthList}, the vertically scrolling list of months. */
72
73
  export type MonthListProps<T> = {
73
74
  /** The month scrolled to on mount. */
74
75
  date: Date;
@@ -155,7 +156,7 @@ function MonthListInner<T>({
155
156
  onSelectDrag,
156
157
  onChangeVisibleMonth,
157
158
  renderMonthHeader,
158
- }: MonthListProps<T>) {
159
+ }: MonthListProps<T>): ReactElement {
159
160
  const theme = useCalendarTheme();
160
161
  const listRef = useRef<LegendListRef>(null);
161
162
 
@@ -553,6 +554,24 @@ function MonthBlock({
553
554
  );
554
555
  }
555
556
 
557
+ /**
558
+ * A vertically scrolling, virtualized list of month grids. It doubles as the
559
+ * date picker: pass `selectedRange`/`selectedDates` and selection handlers to
560
+ * turn it into a single-date or range picker. Free of Reanimated, so the
561
+ * `/picker` entry point can ship it without the timetable views.
562
+ *
563
+ * @example
564
+ * ```tsx
565
+ * import { MonthList } from "react-native-super-calendar/picker";
566
+ *
567
+ * <MonthList
568
+ * date={new Date()}
569
+ * weekStartsOn={0}
570
+ * selectedRange={range}
571
+ * onPressDay={handlePressDay}
572
+ * />
573
+ * ```
574
+ */
556
575
  export const MonthList = memo(MonthListInner) as typeof MonthListInner;
557
576
 
558
577
  const styles = StyleSheet.create({
@@ -5,7 +5,7 @@ import {
5
5
  type OnViewableItemsChangedInfo,
6
6
  } from "@legendapp/list/react-native";
7
7
  import { addMonths, differenceInCalendarMonths, format, type Locale, startOfMonth } from "date-fns";
8
- import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
8
+ import { memo, type ReactElement, useCallback, useEffect, useMemo, useRef, useState } from "react";
9
9
  import {
10
10
  Platform,
11
11
  StyleSheet,
@@ -34,6 +34,7 @@ const PAGE_WINDOW = 60;
34
34
  // paging commits once per settle rather than mid-swipe.
35
35
  const PAGE_VIEWABILITY = { itemVisiblePercentThreshold: 90 };
36
36
 
37
+ /** Props for {@link MonthPager}, the horizontally swipeable month carousel. */
37
38
  export type MonthPagerProps<T> = {
38
39
  date: Date;
39
40
  events: CalendarEvent<T>[];
@@ -90,7 +91,7 @@ function MonthPagerInner<T>({
90
91
  activeDate,
91
92
  renderHeaderForMonthView,
92
93
  renderCustomDateForMonth,
93
- }: MonthPagerProps<T>) {
94
+ }: MonthPagerProps<T>): ReactElement {
94
95
  const theme = useCalendarTheme();
95
96
  const { width, height } = useWindowDimensions();
96
97
  const listRef = useRef<LegendListRef>(null);
@@ -318,6 +319,24 @@ function MonthPagerInner<T>({
318
319
  );
319
320
  }
320
321
 
322
+ /**
323
+ * A horizontally swipeable month carousel. Swipe left/right to page between
324
+ * months; the committed month is reported through `onChangeDate`. It is the
325
+ * Reanimated-driven month view that `Calendar` uses in month mode.
326
+ *
327
+ * @example
328
+ * ```tsx
329
+ * import { MonthPager } from "react-native-super-calendar";
330
+ *
331
+ * <MonthPager
332
+ * date={date}
333
+ * events={events}
334
+ * weekStartsOn={0}
335
+ * onChangeDate={setDate}
336
+ * onPressEvent={(e) => console.log(e.title)}
337
+ * />
338
+ * ```
339
+ */
321
340
  export const MonthPager = memo(MonthPagerInner) as typeof MonthPagerInner;
322
341
 
323
342
  type MonthWeekdayHeaderProps = {
@@ -1,5 +1,5 @@
1
1
  import { format, type Locale, isSameMonth, startOfDay } from "date-fns";
2
- import { memo, useMemo, useState } from "react";
2
+ import { memo, type ReactElement, useMemo, useState } from "react";
3
3
  import {
4
4
  type LayoutChangeEvent,
5
5
  Platform,
@@ -42,6 +42,7 @@ const FALLBACK_VISIBLE_COUNT = 3;
42
42
  const numericStyle = (value: number | string | undefined, fallback: number) =>
43
43
  typeof value === "number" ? value : fallback;
44
44
 
45
+ /** Props for {@link MonthView}, the single-month grid. */
45
46
  export type MonthViewProps<T> = {
46
47
  date: Date;
47
48
  events: CalendarEvent<T>[];
@@ -139,7 +140,7 @@ function MonthViewInner<T>({
139
140
  renderCustomDateForMonth,
140
141
  onDayPointerDown,
141
142
  onDayPointerEnter,
142
- }: MonthViewProps<T>) {
143
+ }: MonthViewProps<T>): ReactElement {
143
144
  const theme = useCalendarTheme();
144
145
  // Selection comes from context (so cached pages still repaint), but explicit
145
146
  // props win for direct/standalone use of MonthView.
@@ -454,6 +455,22 @@ function MonthViewInner<T>({
454
455
  );
455
456
  }
456
457
 
458
+ /**
459
+ * A single month rendered as a 7-column grid of day cells, each showing its
460
+ * event chips with a "+N more" overflow. Render it on its own for a static
461
+ * month, or let `MonthList`/`Calendar` page through months for you.
462
+ *
463
+ * @example
464
+ * ```tsx
465
+ * import { MonthView, type CalendarEvent } from "react-native-super-calendar";
466
+ *
467
+ * <MonthView
468
+ * date={new Date()}
469
+ * events={events}
470
+ * onPressDay={(day) => console.log(day)}
471
+ * />
472
+ * ```
473
+ */
457
474
  export const MonthView = memo(MonthViewInner) as typeof MonthViewInner;
458
475
 
459
476
  const styles = StyleSheet.create({
@@ -15,7 +15,7 @@ import {
15
15
  startOfDay,
16
16
  startOfWeek,
17
17
  } from "date-fns";
18
- import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
18
+ import { memo, type ReactElement, useCallback, useEffect, useMemo, useRef, useState } from "react";
19
19
  import {
20
20
  type GestureResponderEvent,
21
21
  Platform,
@@ -90,6 +90,7 @@ const PAGE_WINDOW = 180;
90
90
  const PAGE_VIEWABILITY = { itemVisiblePercentThreshold: 90 };
91
91
 
92
92
  // Matches the dom renderer's default so both grids start at the same density.
93
+ /** Default height in pixels of one hour row on the time grid. */
93
94
  export const DEFAULT_HOUR_HEIGHT = 48;
94
95
  const DEFAULT_MIN_HOUR_HEIGHT = 32;
95
96
  const DEFAULT_MAX_HOUR_HEIGHT = 160;
@@ -206,23 +207,30 @@ function AnimatedEventBox<T>({
206
207
  const moveOffsetX = useSharedValue(0);
207
208
  const resizeDelta = useSharedValue(0);
208
209
 
210
+ // Pull the geometry out as primitives so the worklets below close over plain
211
+ // numbers, not `positioned` itself. Referencing `positioned.*` inside a
212
+ // worklet captures the whole object, and `positioned.event` holds `Date`s,
213
+ // which react-native-worklets >=0.10 refuses to copy to the UI thread
214
+ // ("Cannot copy value of type `Date`").
215
+ const startHours = positioned.startHours;
216
+ const durationHours = positioned.durationHours;
217
+
209
218
  // Live pixel height of the box, driven on the UI thread by the shared
210
219
  // cellHeight (plus any in-progress resize). Handed to renderEvent so custom
211
220
  // renderers can reveal detail progressively as the grid zooms, without
212
221
  // re-rendering. Explicit deps so the worklet re-captures the event's geometry.
213
222
  const boxHeight = useDerivedValue(
214
- () =>
215
- Math.max(positioned.durationHours * cellHeight.value + resizeDelta.value, MIN_EVENT_HEIGHT),
216
- [positioned.durationHours],
223
+ () => Math.max(durationHours * cellHeight.value + resizeDelta.value, MIN_EVENT_HEIGHT),
224
+ [durationHours],
217
225
  );
218
226
 
219
227
  const boxStyle = useAnimatedStyle(
220
228
  () => ({
221
- top: (positioned.startHours - minHour) * cellHeight.value + moveOffset.value,
229
+ top: (startHours - minHour) * cellHeight.value + moveOffset.value,
222
230
  height: boxHeight.value,
223
231
  transform: [{ translateX: moveOffsetX.value }],
224
232
  }),
225
- [positioned.startHours, positioned.durationHours, minHour],
233
+ [startHours, durationHours, minHour],
226
234
  );
227
235
 
228
236
  // Clear the drag preview once the committed change re-renders this box at its
@@ -730,7 +738,11 @@ function TimetablePageInner<T>({
730
738
  [mode, date, weekStartsOn, numberOfDays, isRTL, weekEndsOn],
731
739
  );
732
740
 
733
- const dayWidth = (width - hourColumnWidth) / days.length;
741
+ // Plain number for worklets to close over: reading `days.length` inside a
742
+ // gesture worklet would capture the whole `days` array (of `Date`s), which
743
+ // react-native-worklets >=0.10 refuses to copy to the UI thread.
744
+ const dayCount = days.length;
745
+ const dayWidth = (width - hourColumnWidth) / dayCount;
734
746
  const dayLeft = (dayIndex: number) => hourColumnWidth + dayIndex * dayWidth;
735
747
 
736
748
  const dayLayouts = useMemo(() => days.map((day) => layoutDayEvents(events, day)), [days, events]);
@@ -848,7 +860,7 @@ function TimetablePageInner<T>({
848
860
  const pan = Gesture.Pan()
849
861
  .enabled(createEnabled)
850
862
  .onStart((event) => {
851
- const idx = days.length === 1 ? 0 : Math.floor(event.x / dayWidth);
863
+ const idx = dayCount === 1 ? 0 : Math.floor(event.x / dayWidth);
852
864
  createDayIndex.value = idx;
853
865
  createStartY.value = event.y;
854
866
  createLeft.value = hourColumnWidth + idx * dayWidth;
@@ -884,7 +896,7 @@ function TimetablePageInner<T>({
884
896
  : pan.activateAfterLongPress(DRAG_ACTIVATE_MS);
885
897
  }, [
886
898
  createEnabled,
887
- days.length,
899
+ dayCount,
888
900
  dayWidth,
889
901
  hourColumnWidth,
890
902
  heightSource,
@@ -1150,6 +1162,7 @@ function TimetablePageInner<T>({
1150
1162
 
1151
1163
  const TimetablePage = memo(TimetablePageInner) as typeof TimetablePageInner;
1152
1164
 
1165
+ /** Props for {@link TimeGrid}, the day/week timetable view. */
1153
1166
  export type TimeGridProps<T> = {
1154
1167
  mode: TimeGridMode;
1155
1168
  /** Day columns to show in `custom` mode. Ignored by day/3days/week. Default 1. */
@@ -1284,7 +1297,7 @@ function TimeGridInner<T>({
1284
1297
  onPressDateHeader,
1285
1298
  onChangeDate,
1286
1299
  renderHeader,
1287
- }: TimeGridProps<T>) {
1300
+ }: TimeGridProps<T>): ReactElement {
1288
1301
  // Guard against an inverted/out-of-range window so the grid never collapses.
1289
1302
  const clampedMinHour = Math.max(0, Math.min(minHour, HOURS_PER_DAY - 1));
1290
1303
  const clampedMaxHour = Math.max(clampedMinHour + 1, Math.min(maxHour, HOURS_PER_DAY));
@@ -1706,6 +1719,25 @@ function TimeGridInner<T>({
1706
1719
  );
1707
1720
  }
1708
1721
 
1722
+ /**
1723
+ * The timetable view used in day, 3days, week, and custom modes: an
1724
+ * hour-by-hour grid with positioned event boxes, an all-day lane, pinch-to-zoom
1725
+ * density, and optional drag-to-move/resize/create. Pages horizontally between
1726
+ * date ranges, reporting the committed range through `onChangeDate`.
1727
+ *
1728
+ * @example
1729
+ * ```tsx
1730
+ * import { TimeGrid } from "react-native-super-calendar";
1731
+ *
1732
+ * <TimeGrid
1733
+ * mode="week"
1734
+ * date={date}
1735
+ * events={events}
1736
+ * onChangeDate={setDate}
1737
+ * onPressEvent={(e) => console.log(e.title)}
1738
+ * />
1739
+ * ```
1740
+ */
1709
1741
  export const TimeGrid = memo(TimeGridInner) as typeof TimeGridInner;
1710
1742
 
1711
1743
  type DefaultHeaderProps = {
package/src/index.tsx CHANGED
@@ -1,3 +1,25 @@
1
+ /**
2
+ * The React Native renderer for super-calendar: gesture-driven, virtualized
3
+ * month, week, and day views, plus the agenda and date picker. Runs on native
4
+ * and on the web through react-native-web.
5
+ *
6
+ * Built on the headless core, Legend List, Gesture Handler, and Reanimated. For
7
+ * a picker-only bundle that does not pull in Reanimated, import from
8
+ * `@super-calendar/native/picker` instead.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * import { Calendar } from "@super-calendar/native";
13
+ *
14
+ * export function App() {
15
+ * return <Calendar mode="month" />;
16
+ * }
17
+ * ```
18
+ *
19
+ * @see https://super-calendar.afonsojramos.me
20
+ *
21
+ * @module
22
+ */
1
23
  export { Calendar, type CalendarProps } from "./components/Calendar";
2
24
  export { Agenda, type AgendaProps } from "./components/Agenda";
3
25
  export { MonthView, type MonthViewProps } from "./components/MonthView";
package/src/picker.tsx CHANGED
@@ -1,7 +1,20 @@
1
- // Date-picker entry point: the month grid, selection, and the headless grid,
2
- // with no dependency on the timetable views or Reanimated. Import from
3
- // "react-native-super-calendar/picker" for a picker-only bundle that doesn't
4
- // require react-native-reanimated. The main entry re-exports everything.
1
+ /**
2
+ * The picker-only entry point for `@super-calendar/native`: the month grid,
3
+ * selection, and the headless grid, with no dependency on the timetable views or
4
+ * Reanimated.
5
+ *
6
+ * Import from `@super-calendar/native/picker` for a lighter bundle that does not
7
+ * require react-native-reanimated. The main entry re-exports everything here.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * import { MonthView } from "@super-calendar/native/picker";
12
+ * ```
13
+ *
14
+ * @see https://super-calendar.afonsojramos.me
15
+ *
16
+ * @module
17
+ */
5
18
  export { MonthView, type MonthViewProps } from "./components/MonthView";
6
19
  export { MonthPager, type MonthPagerProps } from "./components/MonthPager";
7
20
  export { MonthList, type MonthListProps } from "./components/MonthList";
package/src/theme.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type CalendarColors, darkColors, lightColors } from "@super-calendar/core";
2
- import { createContext, useContext } from "react";
2
+ import { type Context, createContext, useContext } from "react";
3
3
  import type { TextStyle } from "react-native";
4
4
 
5
5
  /**
@@ -10,6 +10,7 @@ import type { TextStyle } from "react-native";
10
10
  export interface CalendarTheme {
11
11
  /** The shared colour palette (sourced from `@super-calendar/core`). */
12
12
  colors: CalendarColors;
13
+ /** Text styles for the calendar's labels and the built-in event box. */
13
14
  text: {
14
15
  /** Large day number in the week/day header. */
15
16
  dayNumber: TextStyle;
@@ -33,6 +34,7 @@ export interface CalendarTheme {
33
34
  rangeBandHeight: number;
34
35
  }
35
36
 
37
+ /** The default light theme. Every key the calendar reads falls back to this. */
36
38
  export const defaultTheme: CalendarTheme = {
37
39
  colors: lightColors,
38
40
  text: {
@@ -72,6 +74,11 @@ export function mergeTheme(theme?: PartialCalendarTheme): CalendarTheme {
72
74
  };
73
75
  }
74
76
 
77
+ /**
78
+ * A theme with every key optional. Pass this to `<Calendar theme={...} />` to
79
+ * override only the colours, text styles, or metrics you care about; the rest
80
+ * come from {@link defaultTheme}.
81
+ */
75
82
  export type PartialCalendarTheme = {
76
83
  colors?: Partial<CalendarTheme["colors"]>;
77
84
  text?: Partial<CalendarTheme["text"]>;
@@ -79,8 +86,14 @@ export type PartialCalendarTheme = {
79
86
  rangeBandHeight?: number;
80
87
  };
81
88
 
82
- const CalendarThemeContext = createContext<CalendarTheme>(defaultTheme);
89
+ const CalendarThemeContext: Context<CalendarTheme> = createContext<CalendarTheme>(defaultTheme);
83
90
 
91
+ /**
92
+ * Context provider that supplies the active {@link CalendarTheme} to descendants.
93
+ * `<Calendar>` wraps its subtree in this; use it directly only to theme custom
94
+ * components rendered outside a `<Calendar>`.
95
+ */
84
96
  export const CalendarThemeProvider = CalendarThemeContext.Provider;
85
97
 
86
- export const useCalendarTheme = () => useContext(CalendarThemeContext);
98
+ /** Read the active {@link CalendarTheme} from context. Useful inside custom renderers. */
99
+ export const useCalendarTheme = (): CalendarTheme => useContext(CalendarThemeContext);
package/src/types.ts CHANGED
@@ -20,6 +20,11 @@ export type {
20
20
  // The React Native render contract lives here, not in core: it references
21
21
  // Reanimated (`SharedValue`) and React Native (`ViewStyle`) types, which must
22
22
  // not leak into the platform-free core.
23
+ /**
24
+ * Props passed to a {@link RenderEvent} component for one event. Carries the
25
+ * event, the current mode, press handlers, and built-in renderer hints such as
26
+ * `boxHeight`, `continuesBefore`/`continuesAfter`, and `isAllDay`.
27
+ */
23
28
  export type RenderEventArgs<T = unknown> = {
24
29
  event: CalendarEvent<T>;
25
30
  mode: CalendarMode;