@super-calendar/dom 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@super-calendar/dom",
3
- "version": "2.1.4",
3
+ "version": "2.2.0",
4
4
  "description": "Virtualized month / week / day calendar and date picker for react-dom. No React Native, no react-native-web.",
5
5
  "keywords": [
6
6
  "calendar",
@@ -48,7 +48,7 @@
48
48
  "access": "public"
49
49
  },
50
50
  "dependencies": {
51
- "@super-calendar/core": "2.1.4"
51
+ "@super-calendar/core": "2.2.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@legendapp/list": ">=3",
package/src/Agenda.tsx CHANGED
@@ -1,10 +1,17 @@
1
1
  import { LegendList } from "@legendapp/list/react";
2
2
  import { format, isSameDay, type Locale, startOfDay } from "date-fns";
3
3
  import { type ComponentType, type CSSProperties, type ReactElement, useMemo } from "react";
4
- import type { CalendarEvent } from "@super-calendar/core";
4
+ import type { CalendarEvent, EventAccessibilityLabeler } from "@super-calendar/core";
5
5
  import { eventTimeLabel, getIsToday, isAllDayEvent } from "@super-calendar/core";
6
+ import { createSlots, type ResolvedSlot, type SlotStyleProps } from "./slots";
6
7
  import { type DomCalendarTheme, mergeDomTheme } from "./theme";
7
8
 
9
+ /**
10
+ * Styleable parts of {@link Agenda}. Pass a class or inline style per slot via the
11
+ * `classNames` / `styles` props.
12
+ */
13
+ export type AgendaSlot = "dayHeader" | "eventRow" | "event" | "empty";
14
+
8
15
  /** Props passed to a custom agenda (schedule) event renderer. */
9
16
  export interface DomAgendaEventArgs<T = unknown> {
10
17
  /** The event to render. */
@@ -23,7 +30,7 @@ export interface DomAgendaEventArgs<T = unknown> {
23
30
  export type DomAgendaEvent<T = unknown> = ComponentType<DomAgendaEventArgs<T>>;
24
31
 
25
32
  /** Props for {@link Agenda}. */
26
- export interface AgendaProps<T = unknown> {
33
+ export interface AgendaProps<T = unknown> extends SlotStyleProps<AgendaSlot> {
27
34
  /** Events to list. The consumer controls which (and therefore the date range). */
28
35
  events: CalendarEvent<T>[];
29
36
  /** date-fns locale for the day headers and time labels. */
@@ -38,6 +45,12 @@ export interface AgendaProps<T = unknown> {
38
45
  height?: number | string;
39
46
  /** Replace the built-in event row. */
40
47
  renderEvent?: DomAgendaEvent<T>;
48
+ /**
49
+ * Override the screen-reader label for each event row. Receives the event and a
50
+ * `{ mode: "schedule", isAllDay, ampm }` context; return the full text to
51
+ * announce. Defaults to the row's own text content.
52
+ */
53
+ eventAccessibilityLabel?: EventAccessibilityLabeler<T>;
41
54
  /** Tap an event row. */
42
55
  onPressEvent?: (event: CalendarEvent<T>) => void;
43
56
  /** Tap a day's header. */
@@ -56,8 +69,8 @@ function DefaultAgendaRow<T>({
56
69
  event,
57
70
  isAllDay,
58
71
  ampm = false,
59
- theme,
60
- }: DomAgendaEventArgs<T> & { theme: DomCalendarTheme }) {
72
+ cardProps,
73
+ }: DomAgendaEventArgs<T> & { cardProps: ResolvedSlot }) {
61
74
  const time =
62
75
  eventTimeLabel({
63
76
  mode: "schedule",
@@ -70,14 +83,7 @@ function DefaultAgendaRow<T>({
70
83
  // A filled card with the title on top and the time below, matching the native
71
84
  // schedule row.
72
85
  return (
73
- <div
74
- style={{
75
- padding: "6px 10px",
76
- borderRadius: 8,
77
- background: theme.eventBackground,
78
- color: theme.eventText,
79
- }}
80
- >
86
+ <div {...cardProps}>
81
87
  <div style={{ fontWeight: 600, fontSize: 14 }}>{event.title}</div>
82
88
  {time ? <div style={{ fontSize: 13, opacity: 0.75 }}>{time}</div> : null}
83
89
  </div>
@@ -103,12 +109,16 @@ export function Agenda<T = unknown>({
103
109
  theme: themeOverrides,
104
110
  height = 480,
105
111
  renderEvent,
112
+ eventAccessibilityLabel,
106
113
  onPressEvent,
107
114
  onPressDay,
108
115
  className,
109
116
  style,
117
+ classNames,
118
+ styles,
110
119
  }: AgendaProps<T>): ReactElement {
111
120
  const theme = useMemo(() => mergeDomTheme(themeOverrides), [themeOverrides]);
121
+ const slot = createSlots<AgendaSlot>({ classNames, styles });
112
122
  const Renderer = renderEvent;
113
123
 
114
124
  const rows = useMemo<Row<T>[]>(() => {
@@ -143,24 +153,26 @@ export function Agenda<T = unknown>({
143
153
  : getIsToday(item.date);
144
154
  const label = format(item.date, "EEEE, d LLLL", locale ? { locale } : undefined);
145
155
  const color = highlighted ? theme.todayBackground : theme.textMuted;
156
+ const headerProps = slot("dayHeader", {
157
+ base: onPressDay
158
+ ? {
159
+ display: "block",
160
+ width: "100%",
161
+ border: "none",
162
+ background: "transparent",
163
+ cursor: "pointer",
164
+ font: "inherit",
165
+ textAlign: "left",
166
+ }
167
+ : { display: "block", width: "100%" },
168
+ themed: { padding: "12px 12px 4px", fontSize: 13, fontWeight: 600, color },
169
+ });
146
170
  return onPressDay ? (
147
- <button
148
- type="button"
149
- onClick={() => onPressDay(item.date)}
150
- style={{
151
- ...headerStyle,
152
- color,
153
- border: "none",
154
- background: "transparent",
155
- cursor: "pointer",
156
- font: "inherit",
157
- textAlign: "left",
158
- }}
159
- >
171
+ <button type="button" onClick={() => onPressDay(item.date)} {...headerProps}>
160
172
  {label}
161
173
  </button>
162
174
  ) : (
163
- <div style={{ ...headerStyle, color }}>{label}</div>
175
+ <div {...headerProps}>{label}</div>
164
176
  );
165
177
  }
166
178
  const event = item.event;
@@ -171,33 +183,52 @@ export function Agenda<T = unknown>({
171
183
  <button
172
184
  type="button"
173
185
  onClick={onPress}
174
- style={{
175
- display: "block",
176
- width: "100%",
177
- textAlign: "left",
178
- border: "none",
179
- background: "transparent",
180
- cursor: "pointer",
181
- font: "inherit",
182
- padding: "2px 12px",
183
- }}
186
+ aria-label={
187
+ eventAccessibilityLabel
188
+ ? eventAccessibilityLabel(event, { mode: "schedule", isAllDay, ampm })
189
+ : undefined
190
+ }
191
+ {...slot("eventRow", {
192
+ base: {
193
+ display: "block",
194
+ width: "100%",
195
+ textAlign: "left",
196
+ border: "none",
197
+ background: "transparent",
198
+ cursor: "pointer",
199
+ font: "inherit",
200
+ },
201
+ themed: { padding: "2px 12px" },
202
+ })}
184
203
  >
185
- {Renderer ? <Renderer {...args} /> : <DefaultAgendaRow {...args} theme={theme} />}
204
+ {Renderer ? (
205
+ <Renderer {...args} />
206
+ ) : (
207
+ <DefaultAgendaRow
208
+ {...args}
209
+ cardProps={slot("event", {
210
+ themed: {
211
+ padding: "6px 10px",
212
+ borderRadius: 8,
213
+ background: theme.eventBackground,
214
+ color: theme.eventText,
215
+ },
216
+ })}
217
+ />
218
+ )}
186
219
  </button>
187
220
  );
188
221
  }}
189
222
  />
190
223
  {rows.length === 0 ? (
191
- <div style={{ padding: "16px 12px", color: theme.textMuted, fontSize: 14 }}>No events</div>
224
+ <div
225
+ {...slot("empty", {
226
+ themed: { padding: "16px 12px", color: theme.textMuted, fontSize: 14 },
227
+ })}
228
+ >
229
+ No events
230
+ </div>
192
231
  ) : null}
193
232
  </div>
194
233
  );
195
234
  }
196
-
197
- const headerStyle: CSSProperties = {
198
- display: "block",
199
- width: "100%",
200
- padding: "12px 12px 4px",
201
- fontSize: 13,
202
- fontWeight: 600,
203
- };
package/src/Calendar.tsx CHANGED
@@ -5,16 +5,27 @@ import type {
5
5
  CalendarEvent,
6
6
  DateRange,
7
7
  DateSelectionConstraints,
8
+ EventAccessibilityLabeler,
8
9
  TimeGridMode,
10
+ WeekdayFormat,
9
11
  WeekStartsOn,
10
12
  } from "@super-calendar/core";
11
- import { Agenda, type DomAgendaEvent } from "./Agenda";
12
- import { type DomMonthEvent, MonthView } from "./MonthView";
13
- import { type DomRenderEvent, TimeGrid } from "./TimeGrid";
13
+ import { Agenda, type AgendaSlot, type DomAgendaEvent } from "./Agenda";
14
+ import { type DomMonthEvent, MonthView, type MonthViewSlot } from "./MonthView";
15
+ import type { SlotStyleProps } from "./slots";
16
+ import { type DomRenderEvent, TimeGrid, type TimeGridSlot } from "./TimeGrid";
14
17
  import type { DomCalendarTheme } from "./theme";
15
18
 
19
+ /**
20
+ * Styleable parts across every view {@link Calendar} can render. Only the slots
21
+ * for the active `mode` apply; the rest are ignored. See {@link MonthViewSlot},
22
+ * {@link TimeGridSlot}, and {@link AgendaSlot}.
23
+ */
24
+ export type CalendarSlot = MonthViewSlot | TimeGridSlot | AgendaSlot;
25
+
16
26
  /** Props for {@link Calendar}. */
17
- export interface CalendarProps<T = unknown> extends DateSelectionConstraints {
27
+ export interface CalendarProps<T = unknown>
28
+ extends DateSelectionConstraints, SlotStyleProps<CalendarSlot> {
18
29
  /**
19
30
  * The view to render (default "week"). `month` renders a month grid, `schedule`
20
31
  * a day-grouped agenda list, and the others a time grid.
@@ -41,6 +52,12 @@ export interface CalendarProps<T = unknown> extends DateSelectionConstraints {
41
52
 
42
53
  /** Tap an event (both layouts). */
43
54
  onPressEvent?: (event: CalendarEvent<T>) => void;
55
+ /**
56
+ * Override the screen-reader label for each event. Receives the event and a
57
+ * `{ mode, isAllDay, ampm }` context; return the full text to announce. Defaults
58
+ * to each view's built-in label.
59
+ */
60
+ eventAccessibilityLabel?: EventAccessibilityLabeler<T>;
44
61
 
45
62
  // --- Time-grid modes (week / day / 3days / custom) ---
46
63
  /** 12-hour AM/PM time labels (default false). */
@@ -81,6 +98,8 @@ export interface CalendarProps<T = unknown> extends DateSelectionConstraints {
81
98
  moreLabel?: string;
82
99
  /** Render neighbouring months' days in the leading/trailing cells (default true). */
83
100
  showAdjacentMonths?: boolean;
101
+ /** Weekday header label width: `narrow` ("M"), `short` ("Mon", default), or `long` ("Monday"). */
102
+ weekdayFormat?: WeekdayFormat;
84
103
  /** Fill the cell with the range background instead of the pill band. */
85
104
  fillCellOnSelection?: boolean;
86
105
  /** Selected span. */
@@ -128,6 +147,7 @@ export function Calendar<T = unknown>({
128
147
  className,
129
148
  style,
130
149
  onPressEvent,
150
+ eventAccessibilityLabel,
131
151
  // time grid
132
152
  ampm,
133
153
  hourHeight,
@@ -148,6 +168,7 @@ export function Calendar<T = unknown>({
148
168
  maxVisibleEventCount,
149
169
  moreLabel,
150
170
  showAdjacentMonths,
171
+ weekdayFormat,
151
172
  fillCellOnSelection,
152
173
  selectedRange,
153
174
  selectedDates,
@@ -160,6 +181,9 @@ export function Calendar<T = unknown>({
160
181
  renderMonthEvent,
161
182
  // schedule
162
183
  renderScheduleEvent,
184
+ // styling
185
+ classNames,
186
+ styles,
163
187
  }: CalendarProps<T>): ReactElement {
164
188
  if (mode === "schedule") {
165
189
  return (
@@ -172,7 +196,10 @@ export function Calendar<T = unknown>({
172
196
  activeDate={date}
173
197
  className={className}
174
198
  style={style}
199
+ classNames={classNames}
200
+ styles={styles}
175
201
  renderEvent={renderScheduleEvent}
202
+ eventAccessibilityLabel={eventAccessibilityLabel}
176
203
  onPressEvent={onPressEvent}
177
204
  onPressDay={onPressDay}
178
205
  />
@@ -185,10 +212,13 @@ export function Calendar<T = unknown>({
185
212
  date={date}
186
213
  events={events ?? []}
187
214
  weekStartsOn={weekStartsOn}
215
+ weekdayFormat={weekdayFormat}
188
216
  locale={locale}
189
217
  theme={theme}
190
218
  className={className}
191
219
  style={style}
220
+ classNames={classNames}
221
+ styles={styles}
192
222
  maxVisibleEventCount={maxVisibleEventCount}
193
223
  moreLabel={moreLabel}
194
224
  showAdjacentMonths={showAdjacentMonths}
@@ -200,9 +230,11 @@ export function Calendar<T = unknown>({
200
230
  isDateDisabled={isDateDisabled}
201
231
  keyboardDayNavigation={keyboardDayNavigation}
202
232
  onPressDay={onPressDay}
233
+ onCreateEvent={onCreateEvent}
203
234
  onPressEvent={onPressEvent}
204
235
  onPressMore={onPressMore}
205
236
  renderEvent={renderMonthEvent}
237
+ eventAccessibilityLabel={eventAccessibilityLabel}
206
238
  />
207
239
  );
208
240
  }
@@ -213,12 +245,15 @@ export function Calendar<T = unknown>({
213
245
  mode={mode}
214
246
  events={events}
215
247
  weekStartsOn={weekStartsOn}
248
+ weekdayFormat={weekdayFormat}
216
249
  numberOfDays={numberOfDays}
217
250
  locale={locale}
218
251
  theme={theme}
219
252
  height={height}
220
253
  className={className}
221
254
  style={style}
255
+ classNames={classNames}
256
+ styles={styles}
222
257
  ampm={ampm}
223
258
  hourHeight={hourHeight}
224
259
  scrollOffsetMinutes={scrollOffsetMinutes}
@@ -234,6 +269,7 @@ export function Calendar<T = unknown>({
234
269
  onDragEvent={onDragEvent}
235
270
  onPressDateHeader={onPressDateHeader}
236
271
  renderEvent={renderTimeEvent}
272
+ eventAccessibilityLabel={eventAccessibilityLabel}
237
273
  hourComponent={hourComponent}
238
274
  />
239
275
  );