@super-calendar/native 2.1.4 → 2.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.
@@ -63,8 +63,11 @@ import {
63
63
  snapDeltaMinutes,
64
64
  } from "@super-calendar/core";
65
65
  import { formatHour, layoutDayEvents, type PositionedEvent } from "@super-calendar/core";
66
+ import type { EventAccessibilityLabeler } from "@super-calendar/core";
67
+ import { type WeekdayFormat, weekdayFormatToken } from "@super-calendar/core";
66
68
  import { useWebGridZoom } from "../utils/useWebGridZoom";
67
69
  import { useWebPagerKeys } from "../utils/useWebPagerKeys";
70
+ import { withEventAccessibilityLabel } from "../utils/withEventAccessibilityLabel";
68
71
  import { AllDayLane } from "./AllDayLane";
69
72
 
70
73
  // Horizontal swipe paging doesn't translate to web; there we disable it and page
@@ -370,7 +373,9 @@ function AnimatedEventBox<T>({
370
373
  !draggable && onLongPress ? () => onLongPress(positioned.event) : undefined;
371
374
 
372
375
  const box = (
373
- <Animated.View style={[styles.eventBox, { left, width }, boxStyle]}>
376
+ <Animated.View
377
+ style={[styles.eventBox, { left, width }, boxStyle, theme.containers.timeGridEvent]}
378
+ >
374
379
  <RenderEventComponent
375
380
  event={positioned.event}
376
381
  mode={mode}
@@ -493,6 +498,7 @@ type NowIndicatorProps = {
493
498
  };
494
499
 
495
500
  const NowIndicator = ({ cellHeight, nowHours, minHour, left, width, color }: NowIndicatorProps) => {
501
+ const theme = useCalendarTheme();
496
502
  const animatedStyle = useAnimatedStyle(
497
503
  () => ({ top: (nowHours - minHour) * cellHeight.value }),
498
504
  [nowHours, minHour],
@@ -505,6 +511,7 @@ const NowIndicator = ({ cellHeight, nowHours, minHour, left, width, color }: Now
505
511
  styles.nonInteractive,
506
512
  { left, width, backgroundColor: color },
507
513
  animatedStyle,
514
+ theme.containers.nowIndicator,
508
515
  ]}
509
516
  />
510
517
  );
@@ -1179,7 +1186,15 @@ export type TimeGridProps<T> = {
1179
1186
  /** Initial per-hour row height in px; seeds scroll/zoom without reading the shared value during render. */
1180
1187
  hourHeight?: number;
1181
1188
  weekStartsOn: WeekStartsOn;
1189
+ /** Column-header weekday label width: `narrow` ("M"), `short` ("Mon", default), or `long` ("Monday"). */
1190
+ weekdayFormat?: WeekdayFormat;
1182
1191
  renderEvent: RenderEvent<T>;
1192
+ /**
1193
+ * Override the screen-reader label for each event. Receives the event and a
1194
+ * `{ mode, isAllDay, ampm }` context; return the full text to announce. Defaults
1195
+ * to the built-in title-and-time label.
1196
+ */
1197
+ eventAccessibilityLabel?: EventAccessibilityLabeler<T>;
1183
1198
  keyExtractor: EventKeyExtractor<T>;
1184
1199
  scrollOffsetMinutes?: number;
1185
1200
  hourColumnWidth?: number;
@@ -1258,7 +1273,9 @@ function TimeGridInner<T>({
1258
1273
  cellHeight,
1259
1274
  hourHeight = DEFAULT_HOUR_HEIGHT,
1260
1275
  weekStartsOn,
1276
+ weekdayFormat = "short",
1261
1277
  renderEvent,
1278
+ eventAccessibilityLabel,
1262
1279
  keyExtractor,
1263
1280
  scrollOffsetMinutes = 0,
1264
1281
  hourColumnWidth: hourColumnWidthProp = DEFAULT_HOUR_COLUMN_WIDTH,
@@ -1304,6 +1321,14 @@ function TimeGridInner<T>({
1304
1321
  // Collapse the hour gutter to zero when hours are hidden.
1305
1322
  const hourColumnWidth = hideHours ? 0 : hourColumnWidthProp;
1306
1323
 
1324
+ // Inject a consumer's `eventAccessibilityLabel` override into every event once,
1325
+ // so the timed columns and the all-day lane share it without threading a prop
1326
+ // through each. Passes the grid's `ampm` so the label can match the clock.
1327
+ const labeledRenderEvent = useMemo(
1328
+ () => withEventAccessibilityLabel(renderEvent, eventAccessibilityLabel, ampm),
1329
+ [renderEvent, eventAccessibilityLabel, ampm],
1330
+ );
1331
+
1307
1332
  const { width, height } = useWindowDimensions();
1308
1333
  const listRef = useRef<LegendListRef>(null);
1309
1334
  // The grid's outer view; on web its ref resolves to the DOM node we attach the
@@ -1590,7 +1615,7 @@ function TimeGridInner<T>({
1590
1615
  minHourHeight={minHourHeight}
1591
1616
  maxHourHeight={maxHourHeight}
1592
1617
  showNowIndicator={showNowIndicator}
1593
- renderEvent={renderEvent}
1618
+ renderEvent={labeledRenderEvent}
1594
1619
  keyExtractor={keyExtractor}
1595
1620
  snapMinutes={Math.max(1, dragStepMinutes)}
1596
1621
  showDragHandle={showDragHandle}
@@ -1634,7 +1659,7 @@ function TimeGridInner<T>({
1634
1659
  minHourHeight,
1635
1660
  maxHourHeight,
1636
1661
  showNowIndicator,
1637
- renderEvent,
1662
+ labeledRenderEvent,
1638
1663
  keyExtractor,
1639
1664
  dragStepMinutes,
1640
1665
  showDragHandle,
@@ -1667,6 +1692,7 @@ function TimeGridInner<T>({
1667
1692
  hourColumnWidth={hourColumnWidth}
1668
1693
  showWeekNumber={showWeekNumber}
1669
1694
  weekNumberPrefix={weekNumberPrefix}
1695
+ weekdayFormat={weekdayFormat}
1670
1696
  locale={locale}
1671
1697
  activeDate={activeDate}
1672
1698
  onPressDateHeader={onPressDateHeader}
@@ -1727,7 +1753,7 @@ function TimeGridInner<T>({
1727
1753
  *
1728
1754
  * @example
1729
1755
  * ```tsx
1730
- * import { TimeGrid } from "react-native-super-calendar";
1756
+ * import { TimeGrid } from "@super-calendar/native";
1731
1757
  *
1732
1758
  * <TimeGrid
1733
1759
  * mode="week"
@@ -1747,6 +1773,7 @@ type DefaultHeaderProps = {
1747
1773
  hourColumnWidth: number;
1748
1774
  showWeekNumber?: boolean;
1749
1775
  weekNumberPrefix?: string;
1776
+ weekdayFormat?: WeekdayFormat;
1750
1777
  locale?: Locale;
1751
1778
  activeDate?: Date;
1752
1779
  onPressDateHeader?: (date: Date) => void;
@@ -1759,6 +1786,7 @@ const DefaultHeader = ({
1759
1786
  hourColumnWidth,
1760
1787
  showWeekNumber,
1761
1788
  weekNumberPrefix = "W",
1789
+ weekdayFormat,
1762
1790
  locale,
1763
1791
  activeDate,
1764
1792
  onPressDateHeader,
@@ -1785,6 +1813,7 @@ const DefaultHeader = ({
1785
1813
  day={day}
1786
1814
  mode={mode}
1787
1815
  width={dayWidth}
1816
+ weekdayFormat={weekdayFormat}
1788
1817
  locale={locale}
1789
1818
  activeDate={activeDate}
1790
1819
  onPressDateHeader={onPressDateHeader}
@@ -1798,12 +1827,20 @@ type DayHeaderProps = {
1798
1827
  day: Date;
1799
1828
  mode: CalendarMode;
1800
1829
  width: number;
1830
+ weekdayFormat?: WeekdayFormat;
1801
1831
  locale?: Locale;
1802
1832
  activeDate?: Date;
1803
1833
  onPressDateHeader?: (date: Date) => void;
1804
1834
  };
1805
1835
 
1806
- const DayHeader = ({ day, width, locale, activeDate, onPressDateHeader }: DayHeaderProps) => {
1836
+ const DayHeader = ({
1837
+ day,
1838
+ width,
1839
+ weekdayFormat = "short",
1840
+ locale,
1841
+ activeDate,
1842
+ onPressDateHeader,
1843
+ }: DayHeaderProps) => {
1807
1844
  const theme = useCalendarTheme();
1808
1845
  const isToday = getIsToday(day);
1809
1846
  // Highlight the chosen `activeDate` when supplied, else the real today.
@@ -1811,7 +1848,7 @@ const DayHeader = ({ day, width, locale, activeDate, onPressDateHeader }: DayHea
1811
1848
 
1812
1849
  return (
1813
1850
  <Pressable
1814
- style={[styles.dayHeader, { width }]}
1851
+ style={[styles.dayHeader, { width }, theme.containers.columnHeader]}
1815
1852
  onPress={onPressDateHeader ? () => onPressDateHeader(day) : undefined}
1816
1853
  disabled={!onPressDateHeader}
1817
1854
  accessibilityRole={onPressDateHeader ? "button" : undefined}
@@ -1822,7 +1859,7 @@ const DayHeader = ({ day, width, locale, activeDate, onPressDateHeader }: DayHea
1822
1859
  style={[styles.dayHeaderWeekday, { color: theme.colors.textMuted }]}
1823
1860
  allowFontScaling={false}
1824
1861
  >
1825
- {format(day, "EEE", { locale })}
1862
+ {format(day, weekdayFormatToken(weekdayFormat), { locale })}
1826
1863
  </Text>
1827
1864
  <View
1828
1865
  style={[
package/src/index.tsx CHANGED
@@ -35,6 +35,12 @@ export {
35
35
  } from "./components/TimeGrid";
36
36
  export { DefaultEvent } from "./components/DefaultEvent";
37
37
  export { DefaultMonthEvent } from "./components/DefaultMonthEvent";
38
+ export {
39
+ ResourceTimeline,
40
+ type ResourceTimelineProps,
41
+ type Resource,
42
+ type ResourceEventArgs,
43
+ } from "./components/ResourceTimeline";
38
44
  export {
39
45
  type CalendarTheme,
40
46
  type PartialCalendarTheme,
@@ -48,6 +54,8 @@ export type {
48
54
  BusinessHours,
49
55
  CalendarEvent,
50
56
  CalendarMode,
57
+ EventAccessibilityLabelContext,
58
+ EventAccessibilityLabeler,
51
59
  EventKeyExtractor,
52
60
  ICalendarEvent,
53
61
  RecurrenceFrequency,
@@ -55,6 +63,7 @@ export type {
55
63
  RenderEvent,
56
64
  RenderEventArgs,
57
65
  TimeGridMode,
66
+ WeekdayFormat,
58
67
  WeekStartsOn,
59
68
  } from "./types";
60
69
  export {
@@ -77,6 +86,7 @@ export {
77
86
  type MonthGridWeekday,
78
87
  type UseMonthGridOptions,
79
88
  useMonthGrid,
89
+ weekdayFormatToken,
80
90
  } from "@super-calendar/core";
81
91
  export { expandRecurringEvents } from "@super-calendar/core";
82
92
  export { eventsInTimeZone, toZonedTime } from "@super-calendar/core";
package/src/theme.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type CalendarColors, darkColors, lightColors } from "@super-calendar/core";
2
2
  import { type Context, createContext, useContext } from "react";
3
- import type { TextStyle } from "react-native";
3
+ import type { TextStyle, ViewStyle } from "react-native";
4
4
 
5
5
  /**
6
6
  * The full set of colours, text styles and metrics the calendar paints with.
@@ -16,6 +16,8 @@ export interface CalendarTheme {
16
16
  dayNumber: TextStyle;
17
17
  /** Short weekday label ("Mon") in headers. */
18
18
  weekday: TextStyle;
19
+ /** The "MMMM yyyy" month title above the month grid. */
20
+ monthTitle: TextStyle;
19
21
  /** Date number inside a month cell. */
20
22
  dateCell: TextStyle;
21
23
  /** Hour labels down the left of the time grid. */
@@ -25,6 +27,44 @@ export interface CalendarTheme {
25
27
  /** Title inside the built-in default event box. */
26
28
  eventTitle: TextStyle;
27
29
  };
30
+ /**
31
+ * Per-part `ViewStyle` overrides for the renderer's container elements, the
32
+ * React Native counterpart of the web renderer's per-slot classes. Each is
33
+ * merged onto the built-in style, so you override only what you set. (Rolling
34
+ * out across the views; more slots as they land.)
35
+ */
36
+ containers: {
37
+ /** The month view's outer container (title + weekday header + grid). */
38
+ monthContainer: ViewStyle;
39
+ /** The weekday-label header row above a month grid. */
40
+ weekdayHeader: ViewStyle;
41
+ /** Each week (row of 7 day cells) in the month grid. */
42
+ weekRow: ViewStyle;
43
+ /** Each day cell in the month grid. */
44
+ dayCell: ViewStyle;
45
+ /** The date badge (the circle) inside a month day cell. */
46
+ dayBadge: ViewStyle;
47
+ /** An event chip inside a month day cell. */
48
+ monthEvent: ViewStyle;
49
+ /** Each day's column header in the time grid. */
50
+ columnHeader: ViewStyle;
51
+ /** A timed event's positioned box in the time grid (and resource timeline). */
52
+ timeGridEvent: ViewStyle;
53
+ /** The current-time indicator line. */
54
+ nowIndicator: ViewStyle;
55
+ /** Each resource row in the resource timeline. */
56
+ resourceRow: ViewStyle;
57
+ /** The left-hand resource-label cell in the resource timeline. */
58
+ resourceLabel: ViewStyle;
59
+ /** The schedule/agenda list's outer container. */
60
+ agendaList: ViewStyle;
61
+ /** Each event row in the agenda list. */
62
+ agendaRow: ViewStyle;
63
+ /** The all-day lane above the time grid. */
64
+ allDayLane: ViewStyle;
65
+ /** Each day's column within the all-day lane. */
66
+ allDayColumn: ViewStyle;
67
+ };
28
68
  /** Corner radius of the today badge. Use a large value for a circle. */
29
69
  todayBadgeRadius: number;
30
70
  /**
@@ -40,6 +80,7 @@ export const defaultTheme: CalendarTheme = {
40
80
  text: {
41
81
  dayNumber: { fontSize: 22, fontWeight: "700" },
42
82
  weekday: { fontSize: 13, fontWeight: "700" },
83
+ monthTitle: { fontSize: 17, fontWeight: "700" },
43
84
  dateCell: { fontSize: 13, fontWeight: "700" },
44
85
  hourLabel: { fontSize: 10 },
45
86
  more: { fontSize: 11, fontWeight: "700" },
@@ -47,6 +88,23 @@ export const defaultTheme: CalendarTheme = {
47
88
  // whole number of lines (clipping on a line boundary, never mid-line).
48
89
  eventTitle: { fontSize: 12, fontWeight: "700", lineHeight: 16 },
49
90
  },
91
+ containers: {
92
+ monthContainer: {},
93
+ weekdayHeader: {},
94
+ weekRow: {},
95
+ dayCell: {},
96
+ dayBadge: {},
97
+ monthEvent: {},
98
+ columnHeader: {},
99
+ timeGridEvent: {},
100
+ nowIndicator: {},
101
+ resourceRow: {},
102
+ resourceLabel: {},
103
+ agendaList: {},
104
+ agendaRow: {},
105
+ allDayLane: {},
106
+ allDayColumn: {},
107
+ },
50
108
  todayBadgeRadius: 999,
51
109
  rangeBandHeight: 22,
52
110
  };
@@ -59,6 +117,7 @@ export const defaultTheme: CalendarTheme = {
59
117
  export const darkTheme: CalendarTheme = {
60
118
  colors: darkColors,
61
119
  text: defaultTheme.text,
120
+ containers: defaultTheme.containers,
62
121
  todayBadgeRadius: defaultTheme.todayBadgeRadius,
63
122
  rangeBandHeight: defaultTheme.rangeBandHeight,
64
123
  };
@@ -69,6 +128,7 @@ export function mergeTheme(theme?: PartialCalendarTheme): CalendarTheme {
69
128
  return {
70
129
  colors: { ...defaultTheme.colors, ...theme.colors },
71
130
  text: { ...defaultTheme.text, ...theme.text },
131
+ containers: { ...defaultTheme.containers, ...theme.containers },
72
132
  todayBadgeRadius: theme.todayBadgeRadius ?? defaultTheme.todayBadgeRadius,
73
133
  rangeBandHeight: theme.rangeBandHeight ?? defaultTheme.rangeBandHeight,
74
134
  };
@@ -82,6 +142,7 @@ export function mergeTheme(theme?: PartialCalendarTheme): CalendarTheme {
82
142
  export type PartialCalendarTheme = {
83
143
  colors?: Partial<CalendarTheme["colors"]>;
84
144
  text?: Partial<CalendarTheme["text"]>;
145
+ containers?: Partial<CalendarTheme["containers"]>;
85
146
  todayBadgeRadius?: number;
86
147
  rangeBandHeight?: number;
87
148
  };
package/src/types.ts CHANGED
@@ -9,11 +9,14 @@ export type {
9
9
  BusinessHours,
10
10
  CalendarEvent,
11
11
  CalendarMode,
12
+ EventAccessibilityLabelContext,
13
+ EventAccessibilityLabeler,
12
14
  EventKeyExtractor,
13
15
  ICalendarEvent,
14
16
  RecurrenceFrequency,
15
17
  RecurrenceRule,
16
18
  TimeGridMode,
19
+ WeekdayFormat,
17
20
  WeekStartsOn,
18
21
  } from "@super-calendar/core";
19
22
 
@@ -53,6 +56,12 @@ export type RenderEventArgs<T = unknown> = {
53
56
  allDayLabel?: string;
54
57
  /** Per-event style resolved from `eventCellStyle`; the built-in renderer merges it onto the box. */
55
58
  cellStyle?: StyleProp<ViewStyle>;
59
+ /**
60
+ * Screen-reader label to announce for the event, injected by a consumer's
61
+ * `eventAccessibilityLabel` override. The built-in renderers use it in place of
62
+ * their default label; `undefined` falls back to that default.
63
+ */
64
+ accessibilityLabel?: string;
56
65
  onPress: () => void;
57
66
  /** Wired when the consumer passes `onLongPressEvent`; otherwise undefined. */
58
67
  onLongPress?: () => void;
@@ -0,0 +1,30 @@
1
+ import type { EventAccessibilityLabeler } from "@super-calendar/core";
2
+ import type { RenderEvent, RenderEventArgs } from "../types";
3
+
4
+ /**
5
+ * Wrap an event renderer so it injects a consumer's `eventAccessibilityLabel`
6
+ * override into `RenderEventArgs.accessibilityLabel`. The built-in renderers use
7
+ * that string in place of their default label, and custom renderers can read it
8
+ * too. Returns the renderer unchanged when no override is set, so the common path
9
+ * pays nothing.
10
+ */
11
+ export function withEventAccessibilityLabel<T>(
12
+ renderEvent: RenderEvent<T>,
13
+ labeler: EventAccessibilityLabeler<T> | undefined,
14
+ ampm: boolean,
15
+ ): RenderEvent<T> {
16
+ if (!labeler) return renderEvent;
17
+ const Base = renderEvent;
18
+ return function LabeledEvent(props: RenderEventArgs<T>) {
19
+ return (
20
+ <Base
21
+ {...props}
22
+ accessibilityLabel={labeler(props.event, {
23
+ mode: props.mode,
24
+ isAllDay: props.isAllDay ?? false,
25
+ ampm,
26
+ })}
27
+ />
28
+ );
29
+ };
30
+ }