@super-calendar/native 2.4.0 → 2.6.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.
- package/dist/index.d.mts +80 -5
- package/dist/index.d.ts +80 -5
- package/dist/index.js +475 -105
- package/dist/index.mjs +477 -108
- package/dist/picker.js +1 -1
- package/dist/picker.mjs +1 -1
- package/package.json +2 -2
- package/src/components/Calendar.tsx +32 -2
- package/src/components/ResourceTimeline.tsx +305 -0
- package/src/components/YearView.tsx +276 -0
- package/src/index.tsx +1 -0
- package/dist/{MonthList-zPLklHAY.mjs → MonthList-CMzYn9T0.mjs} +1 -1
- package/dist/{MonthList-Aczb2RSY.js → MonthList-Gu6p64fE.js} +1 -1
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { format, startOfDay, type Locale } from "date-fns";
|
|
2
|
+
import { memo, type ReactElement, useMemo, useState } from "react";
|
|
3
|
+
import {
|
|
4
|
+
type LayoutChangeEvent,
|
|
5
|
+
Pressable,
|
|
6
|
+
ScrollView,
|
|
7
|
+
StyleSheet,
|
|
8
|
+
Text,
|
|
9
|
+
View,
|
|
10
|
+
} from "react-native";
|
|
11
|
+
import { useCalendarTheme } from "../theme";
|
|
12
|
+
import type { CalendarEvent, WeekStartsOn } from "../types";
|
|
13
|
+
import { createSlots, type SlotStyleProps } from "../utils/slots";
|
|
14
|
+
import {
|
|
15
|
+
buildMonthWeeks,
|
|
16
|
+
getIsToday,
|
|
17
|
+
getWeekDays,
|
|
18
|
+
getYearMonths,
|
|
19
|
+
groupEventsByDay,
|
|
20
|
+
isSameCalendarDay,
|
|
21
|
+
weekdayFormatToken,
|
|
22
|
+
} from "@super-calendar/core";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The styleable parts of {@link YearView}. `dayText` and `eventDot` are the
|
|
26
|
+
* text/marker inside a day cell (React Native text colour doesn't inherit).
|
|
27
|
+
*/
|
|
28
|
+
export type YearViewSlot =
|
|
29
|
+
| "grid"
|
|
30
|
+
| "month"
|
|
31
|
+
| "monthTitle"
|
|
32
|
+
| "weekdays"
|
|
33
|
+
| "weekday"
|
|
34
|
+
| "week"
|
|
35
|
+
| "day"
|
|
36
|
+
| "dayText"
|
|
37
|
+
| "eventDot";
|
|
38
|
+
|
|
39
|
+
/** Props for {@link YearView}, the twelve mini-month year grid. */
|
|
40
|
+
export type YearViewProps<T = unknown> = SlotStyleProps<YearViewSlot> & {
|
|
41
|
+
/** Any date inside the year to render. */
|
|
42
|
+
date: Date;
|
|
43
|
+
/** Days holding at least one event get a dot. Omit for a plain year. */
|
|
44
|
+
events?: CalendarEvent<T>[];
|
|
45
|
+
weekStartsOn: WeekStartsOn;
|
|
46
|
+
locale?: Locale;
|
|
47
|
+
/** Highlight this date instead of the real "today". */
|
|
48
|
+
activeDate?: Date;
|
|
49
|
+
/**
|
|
50
|
+
* Smallest width a mini month may take before the grid drops a column
|
|
51
|
+
* (default 150). The grid fits 2–4 columns from the measured width.
|
|
52
|
+
*/
|
|
53
|
+
minMonthWidth?: number;
|
|
54
|
+
/** Tap a day cell (e.g. to drill into the day or month view). */
|
|
55
|
+
onPressDay?: (date: Date) => void;
|
|
56
|
+
/** Tap a month's title (e.g. to jump to that month's view). */
|
|
57
|
+
onPressMonth?: (month: Date) => void;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Seed before the first layout pass so the grid renders immediately (and in
|
|
61
|
+
// tests, where onLayout never fires).
|
|
62
|
+
const FALLBACK_COLUMNS = 3;
|
|
63
|
+
|
|
64
|
+
function YearViewInner<T>({
|
|
65
|
+
date,
|
|
66
|
+
events,
|
|
67
|
+
weekStartsOn,
|
|
68
|
+
locale,
|
|
69
|
+
activeDate,
|
|
70
|
+
minMonthWidth = 150,
|
|
71
|
+
onPressDay,
|
|
72
|
+
onPressMonth,
|
|
73
|
+
classNames,
|
|
74
|
+
styles: styleOverrides,
|
|
75
|
+
}: YearViewProps<T>): ReactElement {
|
|
76
|
+
const theme = useCalendarTheme();
|
|
77
|
+
const slot = createSlots<YearViewSlot>({ classNames, styles: styleOverrides });
|
|
78
|
+
|
|
79
|
+
const [columns, setColumns] = useState(FALLBACK_COLUMNS);
|
|
80
|
+
const handleLayout = (event: LayoutChangeEvent) => {
|
|
81
|
+
const width = event.nativeEvent.layout.width;
|
|
82
|
+
if (width <= 0) return;
|
|
83
|
+
setColumns(Math.max(2, Math.min(4, Math.floor(width / minMonthWidth))));
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const months = useMemo(() => getYearMonths(date), [date]);
|
|
87
|
+
// Weekday initials for one shared header per mini month.
|
|
88
|
+
const weekdayLabels = useMemo(
|
|
89
|
+
() =>
|
|
90
|
+
getWeekDays(date, weekStartsOn).map((d) =>
|
|
91
|
+
format(d, weekdayFormatToken("narrow"), { locale }),
|
|
92
|
+
),
|
|
93
|
+
[date, weekStartsOn, locale],
|
|
94
|
+
);
|
|
95
|
+
// Days that hold at least one event, keyed like `groupEventsByDay`.
|
|
96
|
+
const eventDays = useMemo(
|
|
97
|
+
() => new Set(events && events.length > 0 ? groupEventsByDay(events).keys() : []),
|
|
98
|
+
[events],
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<ScrollView onLayout={handleLayout} showsVerticalScrollIndicator>
|
|
103
|
+
<View {...slot("grid", { base: styles.grid })}>
|
|
104
|
+
{months.map((month) => {
|
|
105
|
+
const weeks = buildMonthWeeks(month, weekStartsOn);
|
|
106
|
+
const title = format(month, "MMMM", { locale });
|
|
107
|
+
const monthLabel = format(month, "MMMM yyyy", { locale });
|
|
108
|
+
const titleText = (
|
|
109
|
+
<Text
|
|
110
|
+
{...slot("monthTitle", {
|
|
111
|
+
base: styles.monthTitle,
|
|
112
|
+
themed: [styles.monthTitleText, { color: theme.colors.todayBackground }],
|
|
113
|
+
})}
|
|
114
|
+
allowFontScaling={false}
|
|
115
|
+
>
|
|
116
|
+
{title}
|
|
117
|
+
</Text>
|
|
118
|
+
);
|
|
119
|
+
return (
|
|
120
|
+
<View
|
|
121
|
+
key={month.toISOString()}
|
|
122
|
+
{...slot("month", { base: [styles.month, { width: `${100 / columns}%` }] })}
|
|
123
|
+
>
|
|
124
|
+
{onPressMonth ? (
|
|
125
|
+
<Pressable
|
|
126
|
+
onPress={() => onPressMonth(month)}
|
|
127
|
+
accessibilityRole="button"
|
|
128
|
+
accessibilityLabel={monthLabel}
|
|
129
|
+
>
|
|
130
|
+
{titleText}
|
|
131
|
+
</Pressable>
|
|
132
|
+
) : (
|
|
133
|
+
<View accessible accessibilityRole="header" accessibilityLabel={monthLabel}>
|
|
134
|
+
{titleText}
|
|
135
|
+
</View>
|
|
136
|
+
)}
|
|
137
|
+
{/* Decorative initials: repeating 84 single letters across the twelve
|
|
138
|
+
mini months would drown a screen reader, so hide the rows (the
|
|
139
|
+
day cells carry full date labels). */}
|
|
140
|
+
<View
|
|
141
|
+
{...slot("weekdays", { base: styles.weekdays })}
|
|
142
|
+
accessibilityElementsHidden
|
|
143
|
+
importantForAccessibility="no-hide-descendants"
|
|
144
|
+
>
|
|
145
|
+
{weekdayLabels.map((label, i) => (
|
|
146
|
+
<Text
|
|
147
|
+
key={i}
|
|
148
|
+
{...slot("weekday", {
|
|
149
|
+
base: styles.weekday,
|
|
150
|
+
themed: [styles.weekdayText, { color: theme.colors.textMuted }],
|
|
151
|
+
})}
|
|
152
|
+
allowFontScaling={false}
|
|
153
|
+
>
|
|
154
|
+
{label}
|
|
155
|
+
</Text>
|
|
156
|
+
))}
|
|
157
|
+
</View>
|
|
158
|
+
{weeks.map((week) => (
|
|
159
|
+
<View key={week[0].toISOString()} {...slot("week", { base: styles.week })}>
|
|
160
|
+
{week.map((day) => {
|
|
161
|
+
// Adjacent-month days keep the grid shape but stay blank,
|
|
162
|
+
// like the mini months of other year views.
|
|
163
|
+
if (day.getMonth() !== month.getMonth()) {
|
|
164
|
+
return <View key={day.toISOString()} style={styles.day} />;
|
|
165
|
+
}
|
|
166
|
+
const isHighlighted = activeDate
|
|
167
|
+
? isSameCalendarDay(day, activeDate)
|
|
168
|
+
: getIsToday(day);
|
|
169
|
+
const hasEvents = eventDays.has(startOfDay(day).toISOString());
|
|
170
|
+
const label = `${format(day, "EEEE, d LLLL yyyy", { locale })}${
|
|
171
|
+
getIsToday(day) ? ", today" : ""
|
|
172
|
+
}${hasEvents ? ", has events" : ""}`;
|
|
173
|
+
const daySlot = slot("day", {
|
|
174
|
+
base: styles.day,
|
|
175
|
+
themed: isHighlighted
|
|
176
|
+
? {
|
|
177
|
+
backgroundColor: theme.colors.todayBackground,
|
|
178
|
+
borderRadius: theme.todayBadgeRadius,
|
|
179
|
+
}
|
|
180
|
+
: undefined,
|
|
181
|
+
});
|
|
182
|
+
const content = (
|
|
183
|
+
<>
|
|
184
|
+
<Text
|
|
185
|
+
{...slot("dayText", {
|
|
186
|
+
themed: [
|
|
187
|
+
styles.dayText,
|
|
188
|
+
{
|
|
189
|
+
color: isHighlighted ? theme.colors.todayText : theme.colors.text,
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
})}
|
|
193
|
+
allowFontScaling={false}
|
|
194
|
+
>
|
|
195
|
+
{day.getDate()}
|
|
196
|
+
</Text>
|
|
197
|
+
{hasEvents ? (
|
|
198
|
+
<View
|
|
199
|
+
{...slot("eventDot", {
|
|
200
|
+
base: styles.eventDot,
|
|
201
|
+
themed: {
|
|
202
|
+
backgroundColor: isHighlighted
|
|
203
|
+
? theme.colors.todayText
|
|
204
|
+
: theme.colors.todayBackground,
|
|
205
|
+
},
|
|
206
|
+
})}
|
|
207
|
+
/>
|
|
208
|
+
) : null}
|
|
209
|
+
</>
|
|
210
|
+
);
|
|
211
|
+
return onPressDay ? (
|
|
212
|
+
<Pressable
|
|
213
|
+
key={day.toISOString()}
|
|
214
|
+
onPress={() => onPressDay(day)}
|
|
215
|
+
accessibilityRole="button"
|
|
216
|
+
accessibilityLabel={label}
|
|
217
|
+
{...daySlot}
|
|
218
|
+
>
|
|
219
|
+
{content}
|
|
220
|
+
</Pressable>
|
|
221
|
+
) : (
|
|
222
|
+
<View
|
|
223
|
+
key={day.toISOString()}
|
|
224
|
+
accessible
|
|
225
|
+
accessibilityLabel={label}
|
|
226
|
+
{...daySlot}
|
|
227
|
+
>
|
|
228
|
+
{content}
|
|
229
|
+
</View>
|
|
230
|
+
);
|
|
231
|
+
})}
|
|
232
|
+
</View>
|
|
233
|
+
))}
|
|
234
|
+
</View>
|
|
235
|
+
);
|
|
236
|
+
})}
|
|
237
|
+
</View>
|
|
238
|
+
</ScrollView>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* A year at a glance: the twelve months as compact mini grids, with today
|
|
244
|
+
* highlighted and a dot under days that hold events. Tap a day or a month
|
|
245
|
+
* title to drill into a denser view. It's the view `Calendar` renders for
|
|
246
|
+
* `mode="year"`.
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```tsx
|
|
250
|
+
* import { YearView } from "@super-calendar/native";
|
|
251
|
+
*
|
|
252
|
+
* <YearView
|
|
253
|
+
* date={new Date()}
|
|
254
|
+
* events={events}
|
|
255
|
+
* weekStartsOn={1}
|
|
256
|
+
* onPressDay={(day) => console.log(day)}
|
|
257
|
+
* />
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
export const YearView = memo(YearViewInner) as typeof YearViewInner;
|
|
261
|
+
|
|
262
|
+
const styles = StyleSheet.create({
|
|
263
|
+
grid: { flexDirection: "row", flexWrap: "wrap" },
|
|
264
|
+
month: { padding: 8 },
|
|
265
|
+
// Structural layout / themed typography split per slot, so a slot class can
|
|
266
|
+
// replace the look without breaking the layout.
|
|
267
|
+
monthTitle: { paddingBottom: 4 },
|
|
268
|
+
monthTitleText: { fontSize: 13, fontWeight: "700" },
|
|
269
|
+
weekdays: { flexDirection: "row" },
|
|
270
|
+
weekday: { flex: 1, textAlign: "center" },
|
|
271
|
+
weekdayText: { fontSize: 9, fontWeight: "600" },
|
|
272
|
+
week: { flexDirection: "row" },
|
|
273
|
+
day: { flex: 1, aspectRatio: 1, alignItems: "center", justifyContent: "center" },
|
|
274
|
+
dayText: { fontSize: 11 },
|
|
275
|
+
eventDot: { position: "absolute", bottom: 1, width: 3, height: 3, borderRadius: 2 },
|
|
276
|
+
});
|
package/src/index.tsx
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
export { Calendar, type CalendarProps, type CalendarSlot } from "./components/Calendar";
|
|
24
24
|
export { Agenda, type AgendaProps, type AgendaSlot } from "./components/Agenda";
|
|
25
25
|
export { MonthView, type MonthViewProps, type MonthViewSlot } from "./components/MonthView";
|
|
26
|
+
export { YearView, type YearViewProps, type YearViewSlot } from "./components/YearView";
|
|
26
27
|
export { type ResolvedSlot, type SlotDefault, type SlotStyleProps } from "./utils/slots";
|
|
27
28
|
export { MonthPager, type MonthPagerProps } from "./components/MonthPager";
|
|
28
29
|
export { MonthList, type MonthListProps, type MonthListSlot } from "./components/MonthList";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { addMonths, differenceInCalendarMonths, format, isSameMonth, startOfDay, startOfMonth } from "date-fns";
|
|
2
2
|
import { createContext, createElement, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { CalendarSelectionProvider, buildMonthWeeks, compareDayEvents, darkColors, dayBadgeKind, daySelectionState, eventAccessibilityLabel, eventTimeLabel, getIsToday, getWeekDays, groupEventsByDay, isAllDayEvent, isDateSelectable, isSameCalendarDay, isWeekend, lightColors, monthEventCapacity, monthVisibleCount, rangeBandKind, titleEllipsizeMode, titleNumberOfLines, useCalendarSelection, weekdayFormatToken } from "@super-calendar/core";
|
|
4
|
-
import { LegendList } from "@legendapp/list/react-native";
|
|
5
4
|
import { Platform, StyleSheet, Text, TouchableOpacity, View, useWindowDimensions } from "react-native";
|
|
6
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { LegendList } from "@legendapp/list/react-native";
|
|
7
7
|
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
|
8
8
|
//#region src/theme.ts
|
|
9
9
|
/** The default light theme. Every key the calendar reads falls back to this. */
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
let date_fns = require("date-fns");
|
|
2
2
|
let react = require("react");
|
|
3
3
|
let _super_calendar_core = require("@super-calendar/core");
|
|
4
|
-
let _legendapp_list_react_native = require("@legendapp/list/react-native");
|
|
5
4
|
let react_native = require("react-native");
|
|
6
5
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
|
+
let _legendapp_list_react_native = require("@legendapp/list/react-native");
|
|
7
7
|
let react_native_gesture_handler = require("react-native-gesture-handler");
|
|
8
8
|
//#region src/theme.ts
|
|
9
9
|
/** The default light theme. Every key the calendar reads falls back to this. */
|