@open-webapp/calendar 0.5.1 → 0.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/README.md +8 -0
- package/dist/core/events.d.ts +2 -0
- package/dist/core/events.js +4 -0
- package/dist/react/CalendarPane.js +40 -40
- package/dist/styles.css +42 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -211,6 +211,8 @@ The exported standalone selector keeps its single-list props and behavior; Selec
|
|
|
211
211
|
|
|
212
212
|
### Host CSS
|
|
213
213
|
|
|
214
|
+
Day ribbon styling: override `--calendar-today-accent` to recolour today's ribbon; `--calendar-day-header-height` tunes the sticky label offset.
|
|
215
|
+
|
|
214
216
|
`styles.css` is self-contained, using component-scoped semantic palette tokens and native-control `color-scheme`. Each pane/selector root resets the full palette; explicit dark overrides and system-only `prefers-color-scheme` rules keep instances isolated. It requires no host CSS variables. Layout/status variables:
|
|
215
217
|
|
|
216
218
|
- `--calendar-footer-height` (default `64px`, set on `.calendar-pane`; override to match host footer)
|
|
@@ -221,6 +223,12 @@ The Unaccepted count badge uses `.calendar-rsvp-chip-badge`, with light and dark
|
|
|
221
223
|
|
|
222
224
|
The shared `.calendar-selector-trigger` uses border-box inline-flex, centered alignment, an 8px gap, 36px minimum height, 8px 12px padding, 6px radius, a 1px solid control border, inherited font and pointer cursor. `.calendar-selector-trigger-label` bounds long labels with ellipsis; `.calendar-selector-chevron` stays 12px and does not shrink or add a tab stop. Existing scoped surface/text/control-border tokens style the resting trigger, the hover token supplies its hover background, and the focus token supplies a 2px `:focus-visible` outline with 2px offset. This approved visual-only exception changes trigger geometry and its derived dropdown position, preserving surrounding layout and theme isolation. Import the shipped stylesheet for both footer and standalone use.
|
|
223
225
|
|
|
226
|
+
## 0.6.0 (minor, additive-visual)
|
|
227
|
+
|
|
228
|
+
- Each day has a thin vertical ribbon left of its events with a rotated `Mmm D` label; today's ribbon uses the new `--calendar-today-accent` token (defaults to the selected-chip colour in light and dark). The sticky day header is unchanged.
|
|
229
|
+
- The RSVP status bar moved from the left edge to the right edge of event rows.
|
|
230
|
+
- New helper `formatRibbonLabel`.
|
|
231
|
+
|
|
224
232
|
## 0.5.1 (patch)
|
|
225
233
|
|
|
226
234
|
- Corrects footer and standalone selector affordance with a padded, rounded 36px-minimum trigger, decorative chevron, themed hover and keyboard-focus treatment, and long-label containment.
|
package/dist/core/events.d.ts
CHANGED
|
@@ -52,6 +52,8 @@ export interface DayGroup {
|
|
|
52
52
|
* to), inserted in correct sorted position.
|
|
53
53
|
*/
|
|
54
54
|
export declare function groupByDay(events: CalendarEvent[], todayIso?: string): DayGroup[];
|
|
55
|
+
/** Short "Mmm D" label for the day ribbon: no year, no Today/Tomorrow. */
|
|
56
|
+
export declare function formatRibbonLabel(dateKey: string): string;
|
|
55
57
|
/**
|
|
56
58
|
* "Today · Sept 26" / "Tomorrow · Sept 27" / else "Sept 28"-style.
|
|
57
59
|
* Includes year only when the event's year differs from today's year.
|
package/dist/core/events.js
CHANGED
|
@@ -168,6 +168,10 @@ function formatMonthDay(dateKey, includeYear) {
|
|
|
168
168
|
const base = `${monthName} ${day}`;
|
|
169
169
|
return includeYear ? `${base}, ${year}` : base;
|
|
170
170
|
}
|
|
171
|
+
/** Short "Mmm D" label for the day ribbon: no year, no Today/Tomorrow. */
|
|
172
|
+
export function formatRibbonLabel(dateKey) {
|
|
173
|
+
return formatMonthDay(dateKey, false);
|
|
174
|
+
}
|
|
171
175
|
function addDaysToIso(dateKey, days) {
|
|
172
176
|
const { year, month, day } = parseIsoDateParts(dateKey);
|
|
173
177
|
const d = new Date(Date.UTC(year, month - 1, day));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
-
import { filterByRsvp, groupByDay, getOrganizerLabel, formatDayHeader, isEventCurrent, findNextUpcomingEvent, getCalendarEventKey, DEFAULT_RSVP_STATUSES, } from '../core/events.js';
|
|
3
|
+
import { filterByRsvp, groupByDay, getOrganizerLabel, formatDayHeader, formatRibbonLabel, isEventCurrent, findNextUpcomingEvent, getCalendarEventKey, DEFAULT_RSVP_STATUSES, } from '../core/events.js';
|
|
4
4
|
import { GroupedCalendarSelector } from './CalendarSelector.js';
|
|
5
5
|
function toggleRsvpStatus(selection, status) {
|
|
6
6
|
const next = new Set(selection);
|
|
@@ -305,45 +305,45 @@ export function CalendarPane(props) {
|
|
|
305
305
|
else {
|
|
306
306
|
dayHeaderRefs.current.delete(group.dateKey);
|
|
307
307
|
}
|
|
308
|
-
}, children: formatDayHeader(group.dateKey, todayIso) }), group.events.map((event) => {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
308
|
+
}, children: formatDayHeader(group.dateKey, todayIso) }), group.events.length > 0 && (_jsxs("div", { className: "calendar-day-body", children: [_jsx("div", { className: "calendar-day-ribbon", "aria-hidden": "true", "data-today": group.dateKey === todayIso ? 'true' : undefined, children: _jsx("span", { className: "calendar-day-ribbon-label", children: formatRibbonLabel(group.dateKey) }) }), _jsx("div", { className: "calendar-day-events", children: group.events.map((event) => {
|
|
309
|
+
const eventKey = getCalendarEventKey(event);
|
|
310
|
+
const calendar = calendarsByKey.get(`${event.sourceId}\u0000${event.calendarId}`);
|
|
311
|
+
const showCalendarLabel = calendar && !calendar.primary;
|
|
312
|
+
const organizerLabel = getOrganizerLabel(event);
|
|
313
|
+
const showOrganizerLabel = multi
|
|
314
|
+
? true
|
|
315
|
+
: organizerLabel && (!showCalendarLabel || organizerLabel !== calendar.summary);
|
|
316
|
+
const organizerText = multi
|
|
317
|
+
? organizerLabel
|
|
318
|
+
? `${organizerLabel} · ${sourceEmail(event.sourceId)}`
|
|
319
|
+
: sourceEmail(event.sourceId)
|
|
320
|
+
: organizerLabel;
|
|
321
|
+
const rsvpStatus = RSVP_CHIPS.some((chip) => chip.status === event.selfResponseStatus)
|
|
322
|
+
? event.selfResponseStatus
|
|
323
|
+
: 'needsAction';
|
|
324
|
+
if (event.isAllDay) {
|
|
325
|
+
return (_jsxs("div", { className: `calendar-event-allday calendar-event-row--rsvp-${rsvpStatus}`, children: [_jsxs("span", { className: "calendar-event-title-wrap", children: [_jsx("span", { className: "calendar-event-title", children: event.summary }), props.renderEventAction?.(event)] }), showCalendarLabel && (_jsxs("span", { className: "calendar-event-calendar", children: [_jsx("span", { className: "calendar-event-calendar-dot", style: { backgroundColor: calendar.backgroundColor } }), calendar.summary] })), showOrganizerLabel && (_jsxs("span", { className: "calendar-event-organizer", children: [multi && (_jsx("span", { className: "calendar-event-source-dot", style: { backgroundColor: sourceColor(event.sourceId) } })), organizerText] }))] }, eventKey));
|
|
326
|
+
}
|
|
327
|
+
const hasLink = usableUrl(event.htmlLink) !== null;
|
|
328
|
+
return (_jsxs("div", { className: `calendar-event-row${currentEventKeys.has(eventKey) ? ' calendar-event-row--current' : ''}${selectedEventKey === eventKey ? ' calendar-event-row--selected' : ''} calendar-event-row--rsvp-${rsvpStatus}`, ref: (el) => {
|
|
329
|
+
if (el) {
|
|
330
|
+
timedEventRefs.current.set(eventKey, el);
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
timedEventRefs.current.delete(eventKey);
|
|
334
|
+
}
|
|
335
|
+
}, children: [_jsxs("div", { className: "calendar-event-header", children: [_jsx("span", { className: "calendar-event-time", children: formatTimeRange(event) }), event.joinUrl && (_jsx("a", { className: "calendar-event-join", href: event.joinUrl, target: "_blank", rel: "noopener noreferrer", onClick: (e) => e.stopPropagation(), children: "Join" }))] }), _jsxs("div", { className: "calendar-event-title-wrap", children: [hasLink ? (_jsx("a", { className: "calendar-event-title-link", href: event.htmlLink, target: "_blank", rel: "noopener noreferrer", onClick: (e) => {
|
|
336
|
+
if (isDesktop &&
|
|
337
|
+
e.button === 0 &&
|
|
338
|
+
!e.ctrlKey &&
|
|
339
|
+
!e.metaKey &&
|
|
340
|
+
!e.shiftKey &&
|
|
341
|
+
!e.altKey) {
|
|
342
|
+
e.preventDefault();
|
|
343
|
+
openEventPopup(event.htmlLink, popupRef, popupName);
|
|
344
|
+
}
|
|
345
|
+
}, children: _jsx("span", { className: "calendar-event-title", children: event.summary }) })) : (_jsx("span", { className: "calendar-event-title", children: event.summary })), props.renderEventAction?.(event)] }), showCalendarLabel && (_jsxs("span", { className: "calendar-event-calendar", children: [_jsx("span", { className: "calendar-event-calendar-dot", style: { backgroundColor: calendar.backgroundColor } }), calendar.summary] })), showOrganizerLabel && (_jsxs("span", { className: "calendar-event-organizer", children: [multi && (_jsx("span", { className: "calendar-event-source-dot", style: { backgroundColor: sourceColor(event.sourceId) } })), organizerText] }))] }, eventKey));
|
|
346
|
+
}) })] }))] }, group.dateKey))), loading && _jsx("p", { className: "calendar-empty-state-text", children: "Loading calendar\u2026" }), filteredEvents.length === 0 && !loading && !error && needsScopeSources.length === 0 && _jsx("p", { className: "calendar-empty-state-text", children: emptyStateText })] }))] }), showCalendarSelector && (_jsx("footer", { className: "calendar-pane-footer", children: _jsx(GroupedCalendarSelector, { theme: theme, groups: calendarSources.map((s) => ({
|
|
347
347
|
id: s.id,
|
|
348
348
|
calendars: controller.getCalendars(s.id),
|
|
349
349
|
selectedIds: controller.getSelectedIds(s.id),
|
package/dist/styles.css
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
--calendar-chip-text: #333;
|
|
12
12
|
--calendar-chip-border: #ccc;
|
|
13
13
|
--calendar-chip-selected: #081a59;
|
|
14
|
+
--calendar-today-accent: #081a59;
|
|
14
15
|
--calendar-chip-selected-text: #ffffff;
|
|
15
16
|
--calendar-badge: #e8eefb;
|
|
16
17
|
--calendar-badge-text: #081a59;
|
|
@@ -193,12 +194,49 @@
|
|
|
193
194
|
z-index: 1;
|
|
194
195
|
}
|
|
195
196
|
|
|
197
|
+
.calendar-day-body {
|
|
198
|
+
display: flex;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.calendar-day-events {
|
|
202
|
+
flex: 1;
|
|
203
|
+
min-width: 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.calendar-day-ribbon {
|
|
207
|
+
flex: 0 0 16px;
|
|
208
|
+
width: 16px;
|
|
209
|
+
min-height: 48px;
|
|
210
|
+
background: var(--calendar-header);
|
|
211
|
+
color: var(--calendar-chip-text);
|
|
212
|
+
user-select: none;
|
|
213
|
+
font-size: 11px;
|
|
214
|
+
font-weight: 600;
|
|
215
|
+
display: flex;
|
|
216
|
+
justify-content: center;
|
|
217
|
+
align-items: flex-start;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.calendar-day-ribbon[data-today='true'] {
|
|
221
|
+
background: var(--calendar-today-accent);
|
|
222
|
+
color: var(--calendar-chip-selected-text);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.calendar-day-ribbon-label {
|
|
226
|
+
writing-mode: vertical-rl;
|
|
227
|
+
transform: rotate(180deg);
|
|
228
|
+
position: sticky;
|
|
229
|
+
top: calc(var(--chip-row-height, 44px) + var(--calendar-day-header-height, 33px));
|
|
230
|
+
white-space: nowrap;
|
|
231
|
+
padding: 6px 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
196
234
|
.calendar-event-allday {
|
|
197
235
|
display: flex;
|
|
198
236
|
align-items: center;
|
|
199
237
|
gap: 8px;
|
|
200
238
|
padding: 10px 16px;
|
|
201
|
-
border-
|
|
239
|
+
border-right: 3px solid var(--rsvp-bar, transparent);
|
|
202
240
|
border-bottom: 1px solid var(--calendar-border);
|
|
203
241
|
}
|
|
204
242
|
|
|
@@ -207,7 +245,7 @@
|
|
|
207
245
|
flex-direction: column;
|
|
208
246
|
gap: 8px;
|
|
209
247
|
padding: 10px 16px;
|
|
210
|
-
border-
|
|
248
|
+
border-right: 3px solid var(--rsvp-bar, transparent);
|
|
211
249
|
border-bottom: 1px solid var(--calendar-border);
|
|
212
250
|
}
|
|
213
251
|
|
|
@@ -350,6 +388,7 @@
|
|
|
350
388
|
--calendar-chip-text: #eee;
|
|
351
389
|
--calendar-chip-border: #444;
|
|
352
390
|
--calendar-chip-selected: #22d0ef;
|
|
391
|
+
--calendar-today-accent: #22d0ef;
|
|
353
392
|
--calendar-chip-selected-text: #081a59;
|
|
354
393
|
--calendar-badge: #081a59;
|
|
355
394
|
--calendar-badge-text: #e8eefb;
|
|
@@ -391,6 +430,7 @@
|
|
|
391
430
|
--calendar-chip-text: #eee;
|
|
392
431
|
--calendar-chip-border: #444;
|
|
393
432
|
--calendar-chip-selected: #22d0ef;
|
|
433
|
+
--calendar-today-accent: #22d0ef;
|
|
394
434
|
--calendar-chip-selected-text: #081a59;
|
|
395
435
|
--calendar-badge: #081a59;
|
|
396
436
|
--calendar-badge-text: #e8eefb;
|