@super-calendar/native 2.3.2 → 2.4.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/README.md +21 -0
- package/dist/{DefaultMonthEvent-BW6qy50X.d.ts → DefaultMonthEvent-BSvuJUFk.d.ts} +174 -173
- package/dist/{DefaultMonthEvent-BbCMc60z.d.mts → DefaultMonthEvent-zDj9dHEv.d.mts} +174 -173
- package/dist/{MonthList-CFXm7BK_.js → MonthList-Aczb2RSY.js} +219 -108
- package/dist/{MonthList-BidXJMfm.mjs → MonthList-zPLklHAY.mjs} +203 -110
- package/dist/index.d.mts +221 -263
- package/dist/index.d.ts +221 -263
- package/dist/index.js +549 -209
- package/dist/index.mjs +542 -214
- package/dist/picker.d.mts +1 -1
- package/dist/picker.d.ts +1 -1
- package/dist/picker.js +1 -1
- package/dist/picker.mjs +1 -1
- package/package.json +2 -2
- package/src/components/Agenda.tsx +52 -11
- package/src/components/AllDayLane.tsx +22 -8
- package/src/components/Calendar.tsx +84 -5
- package/src/components/DefaultEvent.tsx +4 -0
- package/src/components/MonthList.tsx +52 -10
- package/src/components/MonthPager.tsx +45 -6
- package/src/components/MonthView.tsx +133 -74
- package/src/components/ResourceTimeline.tsx +276 -35
- package/src/components/TimeGrid.tsx +282 -162
- package/src/index.tsx +12 -4
- package/src/types.ts +16 -1
- package/src/utils/slots.ts +83 -0
|
@@ -18,8 +18,9 @@ import {
|
|
|
18
18
|
import { useCalendarTheme } from "../theme";
|
|
19
19
|
import type { CalendarEvent, EventKeyExtractor, RenderEvent, WeekStartsOn } from "../types";
|
|
20
20
|
import { type WeekdayFormat, getWeekDays, weekdayFormatToken } from "@super-calendar/core";
|
|
21
|
+
import { createSlots, type SlotStyleProps } from "../utils/slots";
|
|
21
22
|
import { useWebPagerKeys } from "../utils/useWebPagerKeys";
|
|
22
|
-
import { MonthView } from "./MonthView";
|
|
23
|
+
import { MonthView, type MonthViewSlot } from "./MonthView";
|
|
23
24
|
|
|
24
25
|
// Horizontal swipe paging doesn't translate to web; there we disable it and page
|
|
25
26
|
// with the arrow keys instead.
|
|
@@ -64,7 +65,7 @@ export type MonthPagerProps<T> = {
|
|
|
64
65
|
renderHeaderForMonthView?: (weekDays: Date[]) => React.ReactNode;
|
|
65
66
|
/** Replace the default date badge in each day cell. Receives the day. */
|
|
66
67
|
renderCustomDateForMonth?: (date: Date) => React.ReactNode;
|
|
67
|
-
}
|
|
68
|
+
} & SlotStyleProps<MonthViewSlot>;
|
|
68
69
|
|
|
69
70
|
function MonthPagerInner<T>({
|
|
70
71
|
date,
|
|
@@ -93,8 +94,15 @@ function MonthPagerInner<T>({
|
|
|
93
94
|
activeDate,
|
|
94
95
|
renderHeaderForMonthView,
|
|
95
96
|
renderCustomDateForMonth,
|
|
97
|
+
classNames,
|
|
98
|
+
styles: styleOverrides,
|
|
96
99
|
}: MonthPagerProps<T>): ReactElement {
|
|
97
100
|
const theme = useCalendarTheme();
|
|
101
|
+
// Stable across renders (it feeds the memoized renderItem below).
|
|
102
|
+
const slot = useMemo(
|
|
103
|
+
() => createSlots<MonthViewSlot>({ classNames, styles: styleOverrides }),
|
|
104
|
+
[classNames, styleOverrides],
|
|
105
|
+
);
|
|
98
106
|
const { width, height } = useWindowDimensions();
|
|
99
107
|
const listRef = useRef<LegendListRef>(null);
|
|
100
108
|
const containerRef = useRef<View>(null);
|
|
@@ -206,6 +214,14 @@ function MonthPagerInner<T>({
|
|
|
206
214
|
return isRTL ? days.reverse() : days;
|
|
207
215
|
}, [anchor, weekStartsOn, isRTL]);
|
|
208
216
|
|
|
217
|
+
// Pages are keyed by month Dates that never change, so LegendList keeps the
|
|
218
|
+
// pages it has already rendered and only re-renders them when `data` or
|
|
219
|
+
// `extraData` changes — a new `renderItem` identity is not enough. Feed
|
|
220
|
+
// `events` (events that arrive after mount, e.g. from an async fetch, must
|
|
221
|
+
// repaint the mounted page) and `activeDate` (the selected-day highlight must
|
|
222
|
+
// move). Mirrors listExtraData in TimeGrid.
|
|
223
|
+
const listExtraData = useMemo(() => ({ events, activeDate }), [events, activeDate]);
|
|
224
|
+
|
|
209
225
|
const snapToIndices = useMemo(() => monthDates.map((_, index) => index), [monthDates]);
|
|
210
226
|
const keyExtractorList = useCallback((item: Date) => item.toISOString(), []);
|
|
211
227
|
const getFixedItemSize = useCallback(() => containerWidth, [containerWidth]);
|
|
@@ -238,6 +254,8 @@ function MonthPagerInner<T>({
|
|
|
238
254
|
onLongPressEvent={onLongPressEvent}
|
|
239
255
|
onPressMore={onPressMore}
|
|
240
256
|
renderCustomDateForMonth={renderCustomDateForMonth}
|
|
257
|
+
classNames={classNames}
|
|
258
|
+
styles={styleOverrides}
|
|
241
259
|
/>
|
|
242
260
|
</View>
|
|
243
261
|
),
|
|
@@ -264,6 +282,8 @@ function MonthPagerInner<T>({
|
|
|
264
282
|
onLongPressEvent,
|
|
265
283
|
onPressMore,
|
|
266
284
|
renderCustomDateForMonth,
|
|
285
|
+
classNames,
|
|
286
|
+
styleOverrides,
|
|
267
287
|
],
|
|
268
288
|
);
|
|
269
289
|
|
|
@@ -272,7 +292,10 @@ function MonthPagerInner<T>({
|
|
|
272
292
|
{/* The active month's title, above the (shared) weekday header — mirrors the
|
|
273
293
|
dom MonthView's title. The grids below omit their own title/weekdays. */}
|
|
274
294
|
<Text
|
|
275
|
-
|
|
295
|
+
{...slot("title", {
|
|
296
|
+
base: styles.monthTitle,
|
|
297
|
+
themed: [theme.text.monthTitle, { color: theme.colors.text }],
|
|
298
|
+
})}
|
|
276
299
|
allowFontScaling={false}
|
|
277
300
|
>
|
|
278
301
|
{format(date, "MMMM yyyy", locale ? { locale } : undefined)}
|
|
@@ -280,7 +303,12 @@ function MonthPagerInner<T>({
|
|
|
280
303
|
{renderHeaderForMonthView ? (
|
|
281
304
|
renderHeaderForMonthView(weekDays)
|
|
282
305
|
) : (
|
|
283
|
-
<MonthWeekdayHeader
|
|
306
|
+
<MonthWeekdayHeader
|
|
307
|
+
weekDays={weekDays}
|
|
308
|
+
weekdayFormat={weekdayFormat}
|
|
309
|
+
locale={locale}
|
|
310
|
+
slot={slot}
|
|
311
|
+
/>
|
|
284
312
|
)}
|
|
285
313
|
<View
|
|
286
314
|
style={styles.pager}
|
|
@@ -297,6 +325,7 @@ function MonthPagerInner<T>({
|
|
|
297
325
|
ref={listRef}
|
|
298
326
|
style={isWeb ? [styles.pagerList, styles.webNoScroll] : styles.pagerList}
|
|
299
327
|
data={monthDates}
|
|
328
|
+
extraData={listExtraData}
|
|
300
329
|
horizontal
|
|
301
330
|
recycleItems={false}
|
|
302
331
|
keyExtractor={keyExtractorList}
|
|
@@ -348,6 +377,7 @@ type MonthWeekdayHeaderProps = {
|
|
|
348
377
|
weekDays: Date[];
|
|
349
378
|
weekdayFormat?: WeekdayFormat;
|
|
350
379
|
locale?: Locale;
|
|
380
|
+
slot: ReturnType<typeof createSlots<MonthViewSlot>>;
|
|
351
381
|
};
|
|
352
382
|
|
|
353
383
|
// The default weekday-label row above the month grid (e.g. "Mon Tue Wed…"),
|
|
@@ -356,14 +386,23 @@ const MonthWeekdayHeader = ({
|
|
|
356
386
|
weekDays,
|
|
357
387
|
weekdayFormat = "short",
|
|
358
388
|
locale,
|
|
389
|
+
slot,
|
|
359
390
|
}: MonthWeekdayHeaderProps) => {
|
|
360
391
|
const theme = useCalendarTheme();
|
|
361
392
|
return (
|
|
362
|
-
<View
|
|
393
|
+
<View
|
|
394
|
+
{...slot("weekdays", {
|
|
395
|
+
base: styles.weekdayHeader,
|
|
396
|
+
themed: theme.containers.weekdayHeader,
|
|
397
|
+
})}
|
|
398
|
+
>
|
|
363
399
|
{weekDays.map((day) => (
|
|
364
400
|
<Text
|
|
365
401
|
key={day.toISOString()}
|
|
366
|
-
|
|
402
|
+
{...slot("weekday", {
|
|
403
|
+
base: styles.weekdayLabel,
|
|
404
|
+
themed: [theme.text.weekday, { color: theme.colors.textMuted }],
|
|
405
|
+
})}
|
|
367
406
|
allowFontScaling={false}
|
|
368
407
|
>
|
|
369
408
|
{format(day, weekdayFormatToken(weekdayFormat), { locale })}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
const isWeb = Platform.OS === "web";
|
|
17
17
|
import { useCalendarTheme } from "../theme";
|
|
18
18
|
import type { CalendarEvent, EventKeyExtractor, RenderEvent, WeekStartsOn } from "../types";
|
|
19
|
+
import { createSlots, type SlotStyleProps } from "../utils/slots";
|
|
19
20
|
import { withEventAccessibilityLabel } from "../utils/withEventAccessibilityLabel";
|
|
20
21
|
import {
|
|
21
22
|
type DateRange,
|
|
@@ -50,8 +51,26 @@ const FALLBACK_VISIBLE_COUNT = 3;
|
|
|
50
51
|
const numericStyle = (value: number | string | undefined, fallback: number) =>
|
|
51
52
|
typeof value === "number" ? value : fallback;
|
|
52
53
|
|
|
54
|
+
/**
|
|
55
|
+
* The styleable parts of {@link MonthView}. Mirrors the dom renderer's slot
|
|
56
|
+
* names where the structure matches; `dayBadgeText` is native-only (React
|
|
57
|
+
* Native text colour doesn't inherit from the badge). Event chips are styled
|
|
58
|
+
* by `renderEvent` (or the theme), not a slot.
|
|
59
|
+
*/
|
|
60
|
+
export type MonthViewSlot =
|
|
61
|
+
| "title"
|
|
62
|
+
| "weekdays"
|
|
63
|
+
| "weekday"
|
|
64
|
+
| "grid"
|
|
65
|
+
| "week"
|
|
66
|
+
| "day"
|
|
67
|
+
| "dayBadge"
|
|
68
|
+
| "dayBadgeText"
|
|
69
|
+
| "rangeBand"
|
|
70
|
+
| "more";
|
|
71
|
+
|
|
53
72
|
/** Props for {@link MonthView}, the single-month grid. */
|
|
54
|
-
export type MonthViewProps<T> = {
|
|
73
|
+
export type MonthViewProps<T> = SlotStyleProps<MonthViewSlot> & {
|
|
55
74
|
date: Date;
|
|
56
75
|
events: CalendarEvent<T>[];
|
|
57
76
|
/**
|
|
@@ -158,8 +177,11 @@ function MonthViewInner<T>({
|
|
|
158
177
|
renderCustomDateForMonth,
|
|
159
178
|
onDayPointerDown,
|
|
160
179
|
onDayPointerEnter,
|
|
180
|
+
classNames,
|
|
181
|
+
styles: styleOverrides,
|
|
161
182
|
}: MonthViewProps<T>): ReactElement {
|
|
162
183
|
const theme = useCalendarTheme();
|
|
184
|
+
const slot = createSlots<MonthViewSlot>({ classNames, styles: styleOverrides });
|
|
163
185
|
// Selection comes from context (so cached pages still repaint), but explicit
|
|
164
186
|
// props win for direct/standalone use of MonthView.
|
|
165
187
|
const selection = useCalendarSelection();
|
|
@@ -238,17 +260,19 @@ function MonthViewInner<T>({
|
|
|
238
260
|
return (
|
|
239
261
|
<View
|
|
240
262
|
key={day.toISOString()}
|
|
241
|
-
|
|
242
|
-
styles.dayCell,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
263
|
+
{...slot("day", {
|
|
264
|
+
base: styles.dayCell,
|
|
265
|
+
themed: [
|
|
266
|
+
showGrid && {
|
|
267
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
268
|
+
borderRightWidth: StyleSheet.hairlineWidth,
|
|
269
|
+
borderColor: theme.colors.gridLine,
|
|
270
|
+
},
|
|
271
|
+
// No weekend tint on blank placeholders, so the shading doesn't bleed
|
|
272
|
+
// into the empty cells of non-existent days.
|
|
273
|
+
theme.containers.dayCell,
|
|
274
|
+
],
|
|
275
|
+
})}
|
|
252
276
|
/>
|
|
253
277
|
);
|
|
254
278
|
}
|
|
@@ -298,24 +322,29 @@ function MonthViewInner<T>({
|
|
|
298
322
|
const eventCount = dayEvents.length;
|
|
299
323
|
const accessibilityLabel = `${format(day, "EEEE, d LLLL yyyy", { locale })}${isToday ? ", today" : ""}${isSelected ? ", selected" : ""}${isDisabled ? ", unavailable" : ""}, ${eventCount} ${eventCount === 1 ? "event" : "events"}`;
|
|
300
324
|
|
|
325
|
+
const daySlot = slot("day", {
|
|
326
|
+
// Events mode mirrors the dom renderer: left-aligned cell content with
|
|
327
|
+
// the date badge in the top-right. The picker (no grid) stays centered
|
|
328
|
+
// so the selection range band lines up with the centered badge.
|
|
329
|
+
base: [styles.dayCell, showGrid && styles.dayCellEvents],
|
|
330
|
+
themed: [
|
|
331
|
+
showGrid && {
|
|
332
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
333
|
+
borderRightWidth: StyleSheet.hairlineWidth,
|
|
334
|
+
borderColor: theme.colors.gridLine,
|
|
335
|
+
},
|
|
336
|
+
isWeekend(day) && { backgroundColor: theme.colors.weekendBackground },
|
|
337
|
+
theme.containers.dayCell,
|
|
338
|
+
],
|
|
339
|
+
});
|
|
340
|
+
|
|
301
341
|
return (
|
|
302
342
|
<TouchableOpacity
|
|
303
343
|
key={day.toISOString()}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
// so the selection range band lines up with the centered badge.
|
|
309
|
-
showGrid && styles.dayCellEvents,
|
|
310
|
-
showGrid && {
|
|
311
|
-
borderTopWidth: StyleSheet.hairlineWidth,
|
|
312
|
-
borderRightWidth: StyleSheet.hairlineWidth,
|
|
313
|
-
borderColor: theme.colors.gridLine,
|
|
314
|
-
},
|
|
315
|
-
isWeekend(day) && { backgroundColor: theme.colors.weekendBackground },
|
|
316
|
-
calendarCellStyle?.(day),
|
|
317
|
-
theme.containers.dayCell,
|
|
318
|
-
]}
|
|
344
|
+
{...daySlot}
|
|
345
|
+
// The consumer's per-date style is an explicit override, so it merges
|
|
346
|
+
// after the slot (and survives a `day` class).
|
|
347
|
+
style={[daySlot.style, calendarCellStyle?.(day)]}
|
|
319
348
|
// In the picker, don't dim the whole cell on press: the tap should show on
|
|
320
349
|
// the badge (the circle) only, not the cell background. `onPressIn/Out` drive
|
|
321
350
|
// the badge's own opacity below. The events calendar keeps the default
|
|
@@ -360,56 +389,69 @@ function MonthViewInner<T>({
|
|
|
360
389
|
{hasBand ? (
|
|
361
390
|
<View
|
|
362
391
|
testID="month-range-band"
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
392
|
+
{...slot("rangeBand", {
|
|
393
|
+
base: [
|
|
394
|
+
styles.rangeBand,
|
|
395
|
+
{ pointerEvents: "none" },
|
|
396
|
+
fillCellOnSelection
|
|
397
|
+
? { top: 0, bottom: 0 }
|
|
398
|
+
: {
|
|
399
|
+
top: BAND_CENTER_Y - theme.rangeBandHeight / 2,
|
|
400
|
+
height: theme.rangeBandHeight,
|
|
401
|
+
},
|
|
402
|
+
// Cap the pill at the endpoint circle (half a badge in from centre)
|
|
403
|
+
// instead of spilling to the cell edge, so no band shows beside it.
|
|
404
|
+
!fillCellOnSelection &&
|
|
405
|
+
isRangeStart && {
|
|
406
|
+
left: "50%",
|
|
407
|
+
marginLeft: -DATE_BADGE_HEIGHT / 2,
|
|
408
|
+
borderTopLeftRadius: theme.rangeBandHeight / 2,
|
|
409
|
+
borderBottomLeftRadius: theme.rangeBandHeight / 2,
|
|
410
|
+
},
|
|
411
|
+
!fillCellOnSelection &&
|
|
412
|
+
isRangeEnd && {
|
|
413
|
+
right: "50%",
|
|
414
|
+
marginRight: -DATE_BADGE_HEIGHT / 2,
|
|
415
|
+
borderTopRightRadius: theme.rangeBandHeight / 2,
|
|
416
|
+
borderBottomRightRadius: theme.rangeBandHeight / 2,
|
|
417
|
+
},
|
|
418
|
+
],
|
|
419
|
+
themed: { backgroundColor: theme.colors.rangeBackground },
|
|
420
|
+
})}
|
|
387
421
|
/>
|
|
388
422
|
) : null}
|
|
389
423
|
{renderCustomDateForMonth ? (
|
|
390
424
|
renderCustomDateForMonth(day)
|
|
391
425
|
) : (
|
|
392
426
|
<View
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
427
|
+
{...slot("dayBadge", {
|
|
428
|
+
base: [
|
|
429
|
+
styles.dateBadge,
|
|
430
|
+
showGrid && styles.dateBadgeEvents,
|
|
431
|
+
// Tap feedback lives on the badge, not the cell (picker only);
|
|
432
|
+
// kept even under a class so the press still reads.
|
|
433
|
+
!showGrid && pressedKey === dayKey && { opacity: 0.2 },
|
|
434
|
+
],
|
|
435
|
+
themed: [
|
|
436
|
+
isFilledBadge && {
|
|
437
|
+
backgroundColor: isHighlighted
|
|
438
|
+
? theme.colors.todayBackground
|
|
439
|
+
: theme.colors.selectedBackground,
|
|
405
440
|
borderRadius: theme.todayBadgeRadius,
|
|
406
441
|
},
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
442
|
+
isHovered &&
|
|
443
|
+
!isFilledBadge && {
|
|
444
|
+
backgroundColor: theme.colors.hoverBackground,
|
|
445
|
+
borderRadius: theme.todayBadgeRadius,
|
|
446
|
+
},
|
|
447
|
+
theme.containers.dayBadge,
|
|
448
|
+
],
|
|
449
|
+
})}
|
|
411
450
|
>
|
|
412
|
-
<Text
|
|
451
|
+
<Text
|
|
452
|
+
{...slot("dayBadgeText", { themed: [theme.text.dateCell, { color: dateColor }] })}
|
|
453
|
+
allowFontScaling={false}
|
|
454
|
+
>
|
|
413
455
|
{format(day, "d")}
|
|
414
456
|
</Text>
|
|
415
457
|
</View>
|
|
@@ -434,7 +476,10 @@ function MonthViewInner<T>({
|
|
|
434
476
|
))}
|
|
435
477
|
{hiddenCount > 0 ? (
|
|
436
478
|
<Text
|
|
437
|
-
|
|
479
|
+
{...slot("more", {
|
|
480
|
+
base: styles.moreLabel,
|
|
481
|
+
themed: [theme.text.more, { color: theme.colors.textMuted }],
|
|
482
|
+
})}
|
|
438
483
|
onPress={onPressMore ? () => onPressMore(dayEvents, day) : undefined}
|
|
439
484
|
accessibilityRole="button"
|
|
440
485
|
accessibilityLabel={`Show ${hiddenCount} more events`}
|
|
@@ -456,18 +501,29 @@ function MonthViewInner<T>({
|
|
|
456
501
|
<View style={styles.root}>
|
|
457
502
|
{showTitle ? (
|
|
458
503
|
<Text
|
|
459
|
-
|
|
504
|
+
{...slot("title", {
|
|
505
|
+
base: styles.title,
|
|
506
|
+
themed: [theme.text.monthTitle, { color: theme.colors.text }],
|
|
507
|
+
})}
|
|
460
508
|
allowFontScaling={false}
|
|
461
509
|
>
|
|
462
510
|
{format(date, "MMMM yyyy", locale ? { locale } : undefined)}
|
|
463
511
|
</Text>
|
|
464
512
|
) : null}
|
|
465
513
|
{showWeekdays ? (
|
|
466
|
-
<View
|
|
514
|
+
<View
|
|
515
|
+
{...slot("weekdays", {
|
|
516
|
+
base: styles.weekdayHeader,
|
|
517
|
+
themed: theme.containers.weekdayHeader,
|
|
518
|
+
})}
|
|
519
|
+
>
|
|
467
520
|
{weekdayLabels.map((day) => (
|
|
468
521
|
<Text
|
|
469
522
|
key={day.toISOString()}
|
|
470
|
-
|
|
523
|
+
{...slot("weekday", {
|
|
524
|
+
base: styles.weekdayLabel,
|
|
525
|
+
themed: [theme.text.weekday, { color: theme.colors.textMuted }],
|
|
526
|
+
})}
|
|
471
527
|
allowFontScaling={false}
|
|
472
528
|
>
|
|
473
529
|
{format(day, weekdayFormatToken(weekdayFormat), { locale })}
|
|
@@ -475,9 +531,12 @@ function MonthViewInner<T>({
|
|
|
475
531
|
))}
|
|
476
532
|
</View>
|
|
477
533
|
) : null}
|
|
478
|
-
<View
|
|
534
|
+
<View {...slot("grid", { base: styles.container })} onLayout={handleLayout}>
|
|
479
535
|
{weeks.map((week) => (
|
|
480
|
-
<View
|
|
536
|
+
<View
|
|
537
|
+
{...slot("week", { base: styles.weekRow, themed: theme.containers.weekRow })}
|
|
538
|
+
key={week[0].toISOString()}
|
|
539
|
+
>
|
|
481
540
|
{week.map((day) => renderDay(day))}
|
|
482
541
|
</View>
|
|
483
542
|
))}
|