@open-webapp/calendar 0.7.0 → 0.8.1
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 +13 -7
- package/dist/react/CalendarPane.d.ts +2 -2
- package/dist/react/CalendarPane.js +53 -40
- package/dist/styles.css +212 -58
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Multi-account Google Calendar read-only sync (controller) plus React UI (`CalendarPane`, `CalendarSelector`). Storage and encryption are host-supplied adapters.
|
|
4
4
|
|
|
5
|
-
> **0.
|
|
5
|
+
> **0.8.0 is BREAKING.** See [0.8.0](#080-breaking-pre-10-minor) for independent Unaccepted toggling.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -148,7 +148,7 @@ Call at top level of a vitest file; it registers a `describe('StorageAdapter con
|
|
|
148
148
|
| `sources` | `Source[]` | shown accounts; 2+ enables multi-source UI (per-source color and email) |
|
|
149
149
|
| `controller` | `CalendarController` | |
|
|
150
150
|
| `theme?` | `'system' \| 'light' \| 'dark'` | defaults to `'system'`, including when `undefined`; applies to every pane state and all internal selectors |
|
|
151
|
-
| `selectedRsvpStatuses` | `Set<RsvpStatus>` | filter; `DEFAULT_RSVP_STATUSES` = accepted, tentative, needsAction |
|
|
151
|
+
| `selectedRsvpStatuses` | `Set<RsvpStatus>` | host-controlled filter; `DEFAULT_RSVP_STATUSES` = accepted, tentative, needsAction (O pressed) |
|
|
152
152
|
| `onRsvpStatusesChange` | `(next) => void` | |
|
|
153
153
|
| `renderEventAction?` | `(event) => ReactNode` | per-event slot |
|
|
154
154
|
| `connected?` | `boolean` | defaults to `sources.length > 0` (back-compat). `false` shows the not-connected block |
|
|
@@ -162,9 +162,9 @@ Call at top level of a vitest file; it registers a `describe('StorageAdapter con
|
|
|
162
162
|
|
|
163
163
|
#### RSVP chips
|
|
164
164
|
|
|
165
|
-
The chips appear in order: Accepted, Maybe, Declined, Unaccepted.
|
|
165
|
+
The chips appear in order: Accepted, Maybe, Declined, Unaccepted (O). All four independently toggle current supplied membership. Defaults remain accepted, tentative, needsAction, so O is pressed by default.
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
O is pressed iff `needsAction` belongs to the selection, including mixed sets. Unanswered events follow that membership within existing calendar/window filters. Clicking O preserves other statuses; no previous RSVP selection is saved/restored. Removing the last status returns `DEFAULT_RSVP_STATUSES`: O-only -> O returns defaults with O still pressed. Explicit empty host selections remain empty; host rerenders do not normalize membership or emit callbacks.
|
|
168
168
|
|
|
169
169
|
The Unaccepted badge is always visible, including at zero. It counts `needsAction` events from the controller's selected-calendar, rolling ±30-day snapshot before RSVP filtering. Visible counts cap at `99+`; the chip's `aria-label` and `title` expose the full count.
|
|
170
170
|
|
|
@@ -201,7 +201,7 @@ import '@open-webapp/calendar/styles.css';
|
|
|
201
201
|
|
|
202
202
|
### Theme behavior
|
|
203
203
|
|
|
204
|
-
Both components follow the OS/browser `prefers-color-scheme` preference live in `'system'` mode; omitting `theme` preserves this default. Explicit `'light'` and `'dark'` ignore OS changes. Hosts can change the prop on a mounted component (including back to `'system'` or `undefined`) without remounting, closing an open dropdown, losing its search query, focus or keyboard-active option, changing selected calendars, or resetting the pane's
|
|
204
|
+
Both components follow the OS/browser `prefers-color-scheme` preference live in `'system'` mode; omitting `theme` preserves this default. Explicit `'light'` and `'dark'` ignore OS changes. Hosts can change the prop on a mounted component (including back to `'system'` or `undefined`) without remounting, closing an open dropdown, losing its search query, focus or keyboard-active option, changing selected calendars, or resetting the pane's current RSVP selection. Theme changes do not trigger selection callbacks or controller refreshes.
|
|
205
205
|
|
|
206
206
|
Each pane forwards its requested mode to its unified grouped footer selector, including both account groups, account-local actions and the shared search. The single-trigger structure and cross-account keyboard navigation are preserved; the approved trigger geometry exception is described above. Standalone selectors choose their own mode: an omitted/system selector follows the OS even inside an explicitly dark or light pane. Adjacent and nested instances can use opposite modes independently; no document/body theme switch is applied.
|
|
207
207
|
|
|
@@ -211,7 +211,7 @@ The exported standalone selector keeps its single-list props and behavior; Selec
|
|
|
211
211
|
|
|
212
212
|
### Host CSS
|
|
213
213
|
|
|
214
|
-
Day ribbon styling: each day's ribbon alternates between two tones by day parity (`data-parity="even|odd"`), set with `--calendar-day-ribbon-a` and `--calendar-day-ribbon-b`. Parity is stable across scroll, filtering and month boundaries, and the ribbon does not mark today. `--calendar-day-header-height`
|
|
214
|
+
Day ribbon styling: each day's ribbon alternates between two tones by day parity (`data-parity="even|odd"`), set with `--calendar-day-ribbon-a` and `--calendar-day-ribbon-b`. Parity is stable across scroll, filtering and month boundaries, and the ribbon does not mark today. Labels center in the actual ribbon/viewport intersection below the measured chips and that day's header, including empty Today before an event day. Short ribbons center within their bounds. Scroll, resize, rendered-content and mounted theme changes update measurements; `--calendar-day-header-height` no longer positions the label.
|
|
215
215
|
|
|
216
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:
|
|
217
217
|
|
|
@@ -223,6 +223,12 @@ The Unaccepted count badge uses `.calendar-rsvp-chip-badge`, with light and dark
|
|
|
223
223
|
|
|
224
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.
|
|
225
225
|
|
|
226
|
+
## 0.8.0 (BREAKING pre-1.0 minor)
|
|
227
|
+
|
|
228
|
+
- Unaccepted independently toggles `needsAction`, replacing the exclusive shortcut and local restoration introduced in 0.3.0. Hosts relying on pending-only callbacks or restoration must adapt to callbacks reflecting current membership.
|
|
229
|
+
- Defaults and last-deselection fallback remain accepted/tentative/needsAction; O is pressed for defaults and mixed selections containing needsAction.
|
|
230
|
+
- Ribbon labels center in the measured visible intersection, including empty Today, scrolling, resizing and mounted theme changes.
|
|
231
|
+
|
|
226
232
|
## 0.7.0 (minor, breaking-visual)
|
|
227
233
|
|
|
228
234
|
- Day ribbons alternate between two tones by day parity (`data-parity="even|odd"`, days since 1970 of the date key mod 2) instead of highlighting today. Parity is stable across scroll, filter and month boundaries.
|
|
@@ -251,7 +257,7 @@ The shared `.calendar-selector-trigger` uses border-box inline-flex, centered al
|
|
|
251
257
|
|
|
252
258
|
## 0.3.0 (minor, additive)
|
|
253
259
|
|
|
254
|
-
-
|
|
260
|
+
- Added the Unaccepted exclusive shortcut after Accepted, Maybe, and Declined, with local selection restoration and default fallback. This historical behavior is superseded by the independent toggle in 0.8.0.
|
|
255
261
|
- Adds an always-visible Unaccepted count badge, including zero, computed before RSVP filtering from the controller's selected-calendar ±30-day snapshot. Counts above 99 display `99+`, with the full count in the chip's `aria-label` and `title`.
|
|
256
262
|
- Adds the `.calendar-rsvp-chip-badge` host styling hook. Public props and default RSVP statuses remain compatible.
|
|
257
263
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
2
|
import type { CalendarEvent, RsvpStatus, Source } from '../core/types.js';
|
|
3
3
|
import type { CalendarController } from '../core/controller.js';
|
|
4
4
|
export type RsvpSelection = Set<RsvpStatus>;
|
|
@@ -21,4 +21,4 @@ export interface CalendarPaneProps {
|
|
|
21
21
|
/** Window name used for the event popup. */
|
|
22
22
|
popupName?: string;
|
|
23
23
|
}
|
|
24
|
-
export declare function CalendarPane(props: CalendarPaneProps): JSX.Element;
|
|
24
|
+
export declare function CalendarPane(props: CalendarPaneProps): import("react").JSX.Element;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { filterByRsvp, groupByDay, getOrganizerLabel, formatDayHeader, formatRibbonLabel, isEventCurrent, findNextUpcomingEvent, getCalendarEventKey, DEFAULT_RSVP_STATUSES, } from '../core/events.js';
|
|
4
4
|
import { GroupedCalendarSelector } from './CalendarSelector.js';
|
|
@@ -26,24 +26,31 @@ const RSVP_CHIPS = [
|
|
|
26
26
|
{
|
|
27
27
|
status: 'accepted',
|
|
28
28
|
label: 'Accepted',
|
|
29
|
-
|
|
29
|
+
path: 'M5 13l4 4L19 7',
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
status: 'tentative',
|
|
33
33
|
label: 'Maybe',
|
|
34
|
-
|
|
34
|
+
path: 'M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
status: 'declined',
|
|
38
38
|
label: 'Declined',
|
|
39
|
-
|
|
39
|
+
path: 'M6 18L18 6M6 6l12 12',
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
status: 'needsAction',
|
|
43
43
|
label: 'Unaccepted',
|
|
44
|
-
|
|
44
|
+
path: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z',
|
|
45
45
|
},
|
|
46
46
|
];
|
|
47
|
+
function RsvpGlyph({ path, size = 16, strokeWidth = 2.2 }) {
|
|
48
|
+
return (_jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: path, stroke: "currentColor", strokeWidth: strokeWidth, strokeLinecap: "round", strokeLinejoin: "round" }) }));
|
|
49
|
+
}
|
|
50
|
+
function RsvpStatusIcon({ status }) {
|
|
51
|
+
const glyph = RSVP_CHIPS.find((chip) => chip.status === status) ?? RSVP_CHIPS[3];
|
|
52
|
+
return (_jsx("span", { className: `calendar-event-rsvp calendar-event-rsvp--${glyph.status}`, role: "img", "aria-label": glyph.status, title: glyph.status, children: _jsx(RsvpGlyph, { path: glyph.path, size: 14, strokeWidth: 2.4 }) }));
|
|
53
|
+
}
|
|
47
54
|
const DEFAULT_POPUP_NAME = 'notesdiary-cal-event';
|
|
48
55
|
const POPUP_W = 560;
|
|
49
56
|
const POPUP_H = 760;
|
|
@@ -138,12 +145,6 @@ export function CalendarPane(props) {
|
|
|
138
145
|
const scrollRef = useRef(null);
|
|
139
146
|
const popupRef = useRef(null);
|
|
140
147
|
const [now, setNow] = useState(() => new Date());
|
|
141
|
-
const [savedSelection, setSavedSelection] = useState(null);
|
|
142
|
-
const isExclusive = selectedRsvpStatuses.size === 1 && selectedRsvpStatuses.has('needsAction');
|
|
143
|
-
useEffect(() => {
|
|
144
|
-
if (!isExclusive && savedSelection !== null)
|
|
145
|
-
setSavedSelection(null);
|
|
146
|
-
}, [isExclusive, savedSelection]);
|
|
147
148
|
const unacceptedCount = useMemo(() => events.filter((event) => event.selfResponseStatus === 'needsAction').length, [events]);
|
|
148
149
|
const filteredEvents = useMemo(() => filterByRsvp(events, selectedRsvpStatuses), [events, selectedRsvpStatuses]);
|
|
149
150
|
const calendarsByKey = useMemo(() => {
|
|
@@ -166,21 +167,8 @@ export function CalendarPane(props) {
|
|
|
166
167
|
const nextUpcomingEvent = findNextUpcomingEvent(filteredEvents, now);
|
|
167
168
|
const selectedEventKey = currentEventKeys.values().next().value ??
|
|
168
169
|
(nextUpcomingEvent ? getCalendarEventKey(nextUpcomingEvent) : undefined);
|
|
169
|
-
function handleUnaccepted() {
|
|
170
|
-
if (isExclusive) {
|
|
171
|
-
props.onRsvpStatusesChange(savedSelection ?? new Set(DEFAULT_RSVP_STATUSES));
|
|
172
|
-
setSavedSelection(null);
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
setSavedSelection(new Set(selectedRsvpStatuses));
|
|
176
|
-
props.onRsvpStatusesChange(new Set(['needsAction']));
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
170
|
function handleToggle(status) {
|
|
180
|
-
|
|
181
|
-
if (isExclusive)
|
|
182
|
-
setSavedSelection(null);
|
|
183
|
-
props.onRsvpStatusesChange(toggleRsvpStatus(base, status));
|
|
171
|
+
props.onRsvpStatusesChange(toggleRsvpStatus(selectedRsvpStatuses, status));
|
|
184
172
|
}
|
|
185
173
|
const dayGroups = groupByDay(filteredEvents, todayIso);
|
|
186
174
|
function scrollToToday(behavior = 'auto') {
|
|
@@ -277,26 +265,51 @@ export function CalendarPane(props) {
|
|
|
277
265
|
useEffect(() => {
|
|
278
266
|
const chipsEl = chipsRef.current;
|
|
279
267
|
const paneEl = paneRef.current;
|
|
280
|
-
|
|
268
|
+
const scrollEl = scrollRef.current;
|
|
269
|
+
if (!chipsEl || !paneEl || !scrollEl)
|
|
281
270
|
return;
|
|
271
|
+
const ribbons = Array.from(scrollEl.querySelectorAll('.calendar-day-ribbon'));
|
|
272
|
+
function updateRibbonWindows() {
|
|
273
|
+
const viewport = scrollEl.getBoundingClientRect();
|
|
274
|
+
const chipsBottom = chipsEl.getBoundingClientRect().bottom;
|
|
275
|
+
for (const ribbon of ribbons) {
|
|
276
|
+
const bounds = ribbon.getBoundingClientRect();
|
|
277
|
+
const header = ribbon.parentElement?.previousElementSibling;
|
|
278
|
+
const headerBottom = header?.getBoundingClientRect().bottom ?? chipsBottom;
|
|
279
|
+
const top = Math.max(bounds.top, viewport.top, chipsBottom, headerBottom);
|
|
280
|
+
const bottom = Math.min(bounds.bottom, viewport.bottom);
|
|
281
|
+
ribbon.style.setProperty('--calendar-ribbon-window-top', `${Math.min(bounds.height, Math.max(0, top - bounds.top))}px`);
|
|
282
|
+
ribbon.style.setProperty('--calendar-ribbon-window-height', `${Math.max(0, bottom - top)}px`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Observe layout inputs only: the absolutely positioned window cannot resize them.
|
|
282
286
|
const observer = new ResizeObserver((entries) => {
|
|
283
287
|
for (const entry of entries) {
|
|
284
|
-
if (entry.target ===
|
|
285
|
-
// The day ribbon centers its label in the visible scroll area, so it needs that height.
|
|
288
|
+
if (entry.target === scrollEl) {
|
|
286
289
|
paneEl.style.setProperty('--calendar-scroll-height', `${entry.target.getBoundingClientRect().height}px`);
|
|
287
|
-
continue;
|
|
288
290
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
291
|
+
else if (entry.target === chipsEl) {
|
|
292
|
+
// contentRect excludes padding; the sticky day-header offset needs the
|
|
293
|
+
// chip row's full border-box height (chips have vertical padding).
|
|
294
|
+
const height = entry.borderBoxSize?.[0]?.blockSize ?? entry.target.getBoundingClientRect().height;
|
|
295
|
+
paneEl.style.setProperty('--chip-row-height', `${height}px`);
|
|
296
|
+
}
|
|
293
297
|
}
|
|
298
|
+
updateRibbonWindows();
|
|
294
299
|
});
|
|
300
|
+
observer.observe(scrollEl);
|
|
295
301
|
observer.observe(chipsEl);
|
|
296
|
-
|
|
297
|
-
observer.observe(
|
|
298
|
-
|
|
299
|
-
|
|
302
|
+
for (const header of dayHeaderRefs.current.values())
|
|
303
|
+
observer.observe(header);
|
|
304
|
+
for (const ribbon of ribbons)
|
|
305
|
+
observer.observe(ribbon);
|
|
306
|
+
scrollEl.addEventListener('scroll', updateRibbonWindows, { passive: true });
|
|
307
|
+
updateRibbonWindows();
|
|
308
|
+
return () => {
|
|
309
|
+
observer.disconnect();
|
|
310
|
+
scrollEl.removeEventListener('scroll', updateRibbonWindows);
|
|
311
|
+
};
|
|
312
|
+
}, [connected, needsReauth, filteredEvents, todayIso, theme]);
|
|
300
313
|
if (!connected) {
|
|
301
314
|
return (_jsx("div", { className: "calendar-pane", "data-calendar-theme": theme, children: _jsx("div", { className: "calendar-pane-prompt", children: props.emptyState ?? (_jsxs(_Fragment, { children: [_jsx("p", { children: "Not connected to Google Calendar." }), _jsx("button", { className: "calendar-connect-button", onClick: props.onConnect, children: "Connect Google Calendar" })] })) }) }));
|
|
302
315
|
}
|
|
@@ -310,7 +323,7 @@ export function CalendarPane(props) {
|
|
|
310
323
|
const loading = ctrlState.loading && filteredEvents.length === 0;
|
|
311
324
|
const emptyStateText = 'No matching events in the next 30 days';
|
|
312
325
|
const showCalendarSelector = calendarSources.length > 0;
|
|
313
|
-
return (_jsxs("div", { className: "calendar-pane", "data-calendar-theme": theme, ref: paneRef, children: [_jsxs("div", { className: "calendar-pane-scroll", ref: scrollRef, children: [_jsxs("div", { className: "calendar-rsvp-chips", ref: chipsRef, children: [_jsx("
|
|
326
|
+
return (_jsxs("div", { className: "calendar-pane", "data-calendar-theme": theme, ref: paneRef, children: [_jsxs("div", { className: "calendar-pane-scroll", ref: scrollRef, children: [_jsxs("div", { className: "calendar-rsvp-chips", ref: chipsRef, children: [_jsx("div", { className: "calendar-rsvp-chip-group", role: "group", "aria-label": "RSVP Status Filters", children: RSVP_CHIPS.map((chip) => (_jsxs("button", { type: "button", className: `calendar-rsvp-chip${selectedRsvpStatuses.has(chip.status) ? ' selected' : ''}`, "aria-pressed": selectedRsvpStatuses.has(chip.status), "aria-label": chip.status === 'needsAction' ? `${chip.label} (${unacceptedCount})` : chip.label, title: chip.status === 'needsAction' ? `${chip.label} (${unacceptedCount})` : chip.label, onClick: () => handleToggle(chip.status), children: [_jsx(RsvpGlyph, { path: chip.path }), chip.status === 'needsAction' && unacceptedCount > 0 && (_jsx("span", { className: "calendar-rsvp-chip-badge", "aria-hidden": "true", children: unacceptedCount > 99 ? '99+' : unacceptedCount }))] }, chip.status))) }), _jsx("button", { type: "button", className: "calendar-today-btn", "aria-label": "Scroll to today", title: "Scroll to today", onClick: () => recenter('smooth'), children: "Today" })] }), needsScopeSources.map((s) => (_jsx("div", { children: props.sourceNotice?.(s) }, s.id))), error && (_jsx("div", { className: "empty-state calendar-empty-state", role: "alert", children: _jsx("p", { children: "Couldn't load calendar events. Try again shortly." }) })), !(error && filteredEvents.length === 0) && (_jsxs("div", { className: "calendar-day-groups", children: [dayGroups.map((group) => (_jsxs("div", { className: "calendar-day-group", children: [_jsx("div", { className: "calendar-day-header", ref: (el) => {
|
|
314
327
|
if (el) {
|
|
315
328
|
dayHeaderRefs.current.set(group.dateKey, el);
|
|
316
329
|
}
|
|
@@ -334,7 +347,7 @@ export function CalendarPane(props) {
|
|
|
334
347
|
? event.selfResponseStatus
|
|
335
348
|
: 'needsAction';
|
|
336
349
|
if (event.isAllDay) {
|
|
337
|
-
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));
|
|
350
|
+
return (_jsxs("div", { className: `calendar-event-allday calendar-event-row--rsvp-${rsvpStatus}`, children: [_jsxs("div", { className: "calendar-event-header", children: [_jsx("span", { className: "calendar-event-time", children: "All day" }), _jsx(RsvpStatusIcon, { status: event.selfResponseStatus })] }), _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));
|
|
338
351
|
}
|
|
339
352
|
const hasLink = usableUrl(event.htmlLink) !== null;
|
|
340
353
|
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) => {
|
|
@@ -344,7 +357,7 @@ export function CalendarPane(props) {
|
|
|
344
357
|
else {
|
|
345
358
|
timedEventRefs.current.delete(eventKey);
|
|
346
359
|
}
|
|
347
|
-
}, 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) => {
|
|
360
|
+
}, 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" })), _jsx(RsvpStatusIcon, { status: event.selfResponseStatus })] }), _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) => {
|
|
348
361
|
if (isDesktop &&
|
|
349
362
|
e.button === 0 &&
|
|
350
363
|
!e.ctrlKey &&
|
package/dist/styles.css
CHANGED
|
@@ -44,6 +44,39 @@
|
|
|
44
44
|
--calendar-empty: #999;
|
|
45
45
|
--calendar-chip-hover: #00a9ce;
|
|
46
46
|
--calendar-checkbox: #22a6c0;
|
|
47
|
+
--calendar-chip-group-background: #f1f5f9;
|
|
48
|
+
--calendar-chip-group-border: rgb(226 232 240 / 80%);
|
|
49
|
+
--calendar-chip-active-background: #ffffff;
|
|
50
|
+
--calendar-chip-selected-ring: rgb(99 102 241 / 40%);
|
|
51
|
+
--calendar-chip-icon-accepted: #10b981;
|
|
52
|
+
--calendar-chip-icon-tentative: #f59e0b;
|
|
53
|
+
--calendar-chip-icon-declined: #f43f5e;
|
|
54
|
+
--calendar-chip-icon-needsAction: #0ea5e9;
|
|
55
|
+
--calendar-chip-badge-background: #0ea5e9;
|
|
56
|
+
--calendar-chip-badge-text: #ffffff;
|
|
57
|
+
--calendar-chip-badge-border: #ffffff;
|
|
58
|
+
--calendar-today-background: #f1f5f9;
|
|
59
|
+
--calendar-today-hover: #e2e8f0;
|
|
60
|
+
--calendar-today-text: #334155;
|
|
61
|
+
--calendar-card-background: #f8fafc;
|
|
62
|
+
--calendar-card-border: #e2e8f0;
|
|
63
|
+
--calendar-card-title: #0f172a;
|
|
64
|
+
--calendar-card-time: #64748b;
|
|
65
|
+
--calendar-card-active-background: #eef2ff;
|
|
66
|
+
--calendar-card-active-border: #a5b4fc;
|
|
67
|
+
--calendar-card-active-ring: rgb(99 102 241 / 20%);
|
|
68
|
+
--calendar-status-accepted-text: #059669;
|
|
69
|
+
--calendar-status-accepted-background: #ecfdf5;
|
|
70
|
+
--calendar-status-accepted-border: #a7f3d0;
|
|
71
|
+
--calendar-status-tentative-text: #f59e0b;
|
|
72
|
+
--calendar-status-tentative-background: #fffbeb;
|
|
73
|
+
--calendar-status-tentative-border: #fde68a;
|
|
74
|
+
--calendar-status-declined-text: #f43f5e;
|
|
75
|
+
--calendar-status-declined-background: #fff1f2;
|
|
76
|
+
--calendar-status-declined-border: #fecdd3;
|
|
77
|
+
--calendar-status-needsAction-text: #0ea5e9;
|
|
78
|
+
--calendar-status-needsAction-background: #f0f9ff;
|
|
79
|
+
--calendar-status-needsAction-border: #bae6fd;
|
|
47
80
|
color-scheme: var(--calendar-scheme);
|
|
48
81
|
color: var(--calendar-text);
|
|
49
82
|
}
|
|
@@ -112,6 +145,9 @@
|
|
|
112
145
|
|
|
113
146
|
.calendar-rsvp-chips {
|
|
114
147
|
display: flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
box-sizing: border-box;
|
|
150
|
+
min-height: 64px;
|
|
115
151
|
gap: 8px;
|
|
116
152
|
padding: 12px 16px;
|
|
117
153
|
flex-wrap: wrap;
|
|
@@ -121,51 +157,101 @@
|
|
|
121
157
|
z-index: 2;
|
|
122
158
|
}
|
|
123
159
|
|
|
160
|
+
.calendar-rsvp-chip-group {
|
|
161
|
+
display: flex;
|
|
162
|
+
align-items: center;
|
|
163
|
+
flex-shrink: 0;
|
|
164
|
+
gap: 4px;
|
|
165
|
+
padding: 4px;
|
|
166
|
+
border: 1px solid var(--calendar-chip-group-border);
|
|
167
|
+
border-radius: 12px;
|
|
168
|
+
background: var(--calendar-chip-group-background);
|
|
169
|
+
}
|
|
170
|
+
|
|
124
171
|
.calendar-rsvp-chip {
|
|
125
|
-
|
|
126
|
-
min-width: 32px;
|
|
127
|
-
height: 32px;
|
|
172
|
+
position: relative;
|
|
128
173
|
box-sizing: border-box;
|
|
129
174
|
flex-shrink: 0;
|
|
130
175
|
display: flex;
|
|
131
176
|
align-items: center;
|
|
132
177
|
justify-content: center;
|
|
133
|
-
|
|
134
|
-
padding: 0 8px;
|
|
178
|
+
padding: 6px;
|
|
135
179
|
background: transparent;
|
|
136
|
-
|
|
137
|
-
border:
|
|
138
|
-
border-radius: 999px;
|
|
180
|
+
border: 0;
|
|
181
|
+
border-radius: 8px;
|
|
139
182
|
cursor: pointer;
|
|
140
183
|
}
|
|
141
184
|
|
|
142
|
-
.calendar-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
185
|
+
.calendar-rsvp-chip svg {
|
|
186
|
+
width: 16px;
|
|
187
|
+
height: 16px;
|
|
188
|
+
stroke-width: 2.2;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.calendar-rsvp-chip[aria-label='Accepted'] {
|
|
192
|
+
color: var(--calendar-chip-icon-accepted);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.calendar-rsvp-chip[aria-label='Maybe'] {
|
|
196
|
+
color: var(--calendar-chip-icon-tentative);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.calendar-rsvp-chip[aria-label='Declined'] {
|
|
200
|
+
color: var(--calendar-chip-icon-declined);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.calendar-rsvp-chip[aria-label^='Unaccepted'] {
|
|
204
|
+
color: var(--calendar-chip-icon-needsAction);
|
|
146
205
|
}
|
|
147
206
|
|
|
148
207
|
.calendar-rsvp-chip:hover {
|
|
149
|
-
|
|
208
|
+
background: var(--calendar-chip-active-background);
|
|
150
209
|
}
|
|
151
210
|
|
|
152
|
-
.calendar-rsvp-chip.selected {
|
|
153
|
-
background: var(--calendar-chip-
|
|
154
|
-
|
|
155
|
-
border-color: var(--calendar-chip-selected);
|
|
211
|
+
.calendar-rsvp-chip-group .calendar-rsvp-chip.selected {
|
|
212
|
+
background: var(--calendar-chip-active-background);
|
|
213
|
+
box-shadow: 0 0 0 1px var(--calendar-chip-selected-ring);
|
|
156
214
|
}
|
|
157
215
|
|
|
158
216
|
.calendar-rsvp-chip-badge {
|
|
159
|
-
|
|
160
|
-
|
|
217
|
+
position: absolute;
|
|
218
|
+
top: -4px;
|
|
219
|
+
right: -4px;
|
|
220
|
+
box-sizing: border-box;
|
|
221
|
+
display: flex;
|
|
222
|
+
align-items: center;
|
|
223
|
+
justify-content: center;
|
|
224
|
+
min-width: 15px;
|
|
225
|
+
height: 15px;
|
|
226
|
+
padding: 0 2px;
|
|
161
227
|
border-radius: 999px;
|
|
162
|
-
|
|
163
|
-
|
|
228
|
+
border: 1px solid var(--calendar-chip-badge-border);
|
|
229
|
+
background: var(--calendar-chip-badge-background);
|
|
230
|
+
color: var(--calendar-chip-badge-text);
|
|
231
|
+
font-size: 9px;
|
|
232
|
+
font-weight: 700;
|
|
233
|
+
line-height: 1;
|
|
234
|
+
text-align: center;
|
|
235
|
+
white-space: nowrap;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.calendar-today-btn {
|
|
239
|
+
margin-left: auto;
|
|
240
|
+
flex-shrink: 0;
|
|
241
|
+
padding: 4px 8px;
|
|
242
|
+
border: 0;
|
|
243
|
+
border-radius: 6px;
|
|
244
|
+
background: var(--calendar-today-background);
|
|
245
|
+
color: var(--calendar-today-text);
|
|
246
|
+
font-family: inherit;
|
|
164
247
|
font-size: 11px;
|
|
165
248
|
font-weight: 600;
|
|
166
249
|
line-height: 16px;
|
|
167
|
-
|
|
168
|
-
|
|
250
|
+
cursor: pointer;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.calendar-today-btn:hover {
|
|
254
|
+
background: var(--calendar-today-hover);
|
|
169
255
|
}
|
|
170
256
|
|
|
171
257
|
.calendar-empty-state {
|
|
@@ -202,9 +288,14 @@
|
|
|
202
288
|
.calendar-day-events {
|
|
203
289
|
flex: 1;
|
|
204
290
|
min-width: 0;
|
|
291
|
+
display: flex;
|
|
292
|
+
flex-direction: column;
|
|
293
|
+
gap: 8px;
|
|
294
|
+
padding: 12px;
|
|
205
295
|
}
|
|
206
296
|
|
|
207
297
|
.calendar-day-ribbon {
|
|
298
|
+
position: relative;
|
|
208
299
|
flex: 0 0 16px;
|
|
209
300
|
width: 16px;
|
|
210
301
|
min-height: 48px;
|
|
@@ -227,10 +318,9 @@
|
|
|
227
318
|
}
|
|
228
319
|
|
|
229
320
|
.calendar-day-ribbon-window {
|
|
230
|
-
position:
|
|
231
|
-
top:
|
|
232
|
-
|
|
233
|
-
height: min(100%, calc(var(--calendar-scroll-height, 100vh) - var(--chip-row-height, 44px) - var(--calendar-day-header-height, 33px)));
|
|
321
|
+
position: absolute;
|
|
322
|
+
top: var(--calendar-ribbon-window-top, 0px);
|
|
323
|
+
height: var(--calendar-ribbon-window-height, 100%);
|
|
234
324
|
display: flex;
|
|
235
325
|
align-items: center;
|
|
236
326
|
}
|
|
@@ -242,43 +332,50 @@
|
|
|
242
332
|
padding: 6px 0;
|
|
243
333
|
}
|
|
244
334
|
|
|
245
|
-
.calendar-event-allday
|
|
246
|
-
display: flex;
|
|
247
|
-
align-items: center;
|
|
248
|
-
gap: 8px;
|
|
249
|
-
padding: 10px 16px;
|
|
250
|
-
border-right: 3px solid var(--rsvp-bar, transparent);
|
|
251
|
-
border-bottom: 1px solid var(--calendar-border);
|
|
252
|
-
}
|
|
253
|
-
|
|
335
|
+
.calendar-event-allday,
|
|
254
336
|
.calendar-event-row {
|
|
255
337
|
display: flex;
|
|
256
338
|
flex-direction: column;
|
|
257
|
-
gap:
|
|
258
|
-
padding: 10px
|
|
259
|
-
border
|
|
260
|
-
border-
|
|
339
|
+
gap: 6px;
|
|
340
|
+
padding: 10px;
|
|
341
|
+
border: 1px solid var(--calendar-card-border);
|
|
342
|
+
border-radius: 12px;
|
|
343
|
+
background: var(--calendar-card-background);
|
|
261
344
|
}
|
|
262
345
|
|
|
263
|
-
.calendar-event-
|
|
264
|
-
|
|
265
|
-
|
|
346
|
+
.calendar-event-rsvp {
|
|
347
|
+
display: inline-flex;
|
|
348
|
+
flex: none;
|
|
349
|
+
margin-left: auto;
|
|
350
|
+
padding: 4px;
|
|
351
|
+
border: 1px solid var(--status-border);
|
|
352
|
+
border-radius: 4px;
|
|
353
|
+
background: var(--status-background);
|
|
354
|
+
color: var(--status-text);
|
|
266
355
|
}
|
|
267
356
|
|
|
268
|
-
.calendar-event-
|
|
269
|
-
--
|
|
357
|
+
.calendar-event-rsvp--accepted {
|
|
358
|
+
--status-text: var(--calendar-status-accepted-text);
|
|
359
|
+
--status-background: var(--calendar-status-accepted-background);
|
|
360
|
+
--status-border: var(--calendar-status-accepted-border);
|
|
270
361
|
}
|
|
271
362
|
|
|
272
|
-
.calendar-event-
|
|
273
|
-
--
|
|
363
|
+
.calendar-event-rsvp--tentative {
|
|
364
|
+
--status-text: var(--calendar-status-tentative-text);
|
|
365
|
+
--status-background: var(--calendar-status-tentative-background);
|
|
366
|
+
--status-border: var(--calendar-status-tentative-border);
|
|
274
367
|
}
|
|
275
368
|
|
|
276
|
-
.calendar-event-
|
|
277
|
-
--
|
|
369
|
+
.calendar-event-rsvp--needsAction {
|
|
370
|
+
--status-text: var(--calendar-status-needsAction-text);
|
|
371
|
+
--status-background: var(--calendar-status-needsAction-background);
|
|
372
|
+
--status-border: var(--calendar-status-needsAction-border);
|
|
278
373
|
}
|
|
279
374
|
|
|
280
|
-
.calendar-event-
|
|
281
|
-
--
|
|
375
|
+
.calendar-event-rsvp--declined {
|
|
376
|
+
--status-text: var(--calendar-status-declined-text);
|
|
377
|
+
--status-background: var(--calendar-status-declined-background);
|
|
378
|
+
--status-border: var(--calendar-status-declined-border);
|
|
282
379
|
}
|
|
283
380
|
|
|
284
381
|
.calendar-event-header {
|
|
@@ -334,15 +431,18 @@
|
|
|
334
431
|
}
|
|
335
432
|
|
|
336
433
|
.calendar-event-time {
|
|
337
|
-
font-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
434
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
|
435
|
+
font-size: 10px;
|
|
436
|
+
font-weight: 600;
|
|
437
|
+
color: var(--calendar-card-time);
|
|
438
|
+
min-width: 0;
|
|
341
439
|
}
|
|
342
440
|
|
|
343
441
|
.calendar-event-title {
|
|
344
|
-
font-size:
|
|
345
|
-
|
|
442
|
+
font-size: 12px;
|
|
443
|
+
font-weight: 600;
|
|
444
|
+
line-height: 1.25;
|
|
445
|
+
color: var(--calendar-card-title);
|
|
346
446
|
}
|
|
347
447
|
|
|
348
448
|
.calendar-event-organizer {
|
|
@@ -382,7 +482,9 @@
|
|
|
382
482
|
|
|
383
483
|
.calendar-event-row--current,
|
|
384
484
|
.calendar-event-row--selected {
|
|
385
|
-
|
|
485
|
+
border-color: var(--calendar-card-active-border);
|
|
486
|
+
box-shadow: 0 0 0 1px var(--calendar-card-active-ring);
|
|
487
|
+
background: var(--calendar-card-active-background);
|
|
386
488
|
}
|
|
387
489
|
|
|
388
490
|
/* Explicit dark and system-dark use exactly the same palette. The media query
|
|
@@ -427,6 +529,32 @@
|
|
|
427
529
|
--calendar-input-border-style: solid;
|
|
428
530
|
--calendar-option-hover: #2a2a2a;
|
|
429
531
|
--calendar-option-active: #10262b;
|
|
532
|
+
--calendar-chip-group-background: rgb(30 41 59 / 90%);
|
|
533
|
+
--calendar-chip-group-border: rgb(51 65 85 / 60%);
|
|
534
|
+
--calendar-chip-active-background: #334155;
|
|
535
|
+
--calendar-chip-badge-border: #1e293b;
|
|
536
|
+
--calendar-today-background: #1e293b;
|
|
537
|
+
--calendar-today-hover: #334155;
|
|
538
|
+
--calendar-today-text: #e2e8f0;
|
|
539
|
+
--calendar-card-background: rgb(30 41 59 / 80%);
|
|
540
|
+
--calendar-card-border: #334155;
|
|
541
|
+
--calendar-card-title: #ffffff;
|
|
542
|
+
--calendar-card-time: #94a3b8;
|
|
543
|
+
--calendar-card-active-background: rgb(30 27 75 / 80%);
|
|
544
|
+
--calendar-card-active-border: #3730a3;
|
|
545
|
+
--calendar-card-active-ring: rgb(129 140 248 / 30%);
|
|
546
|
+
--calendar-status-accepted-text: #34d399;
|
|
547
|
+
--calendar-status-accepted-background: rgb(2 44 34 / 80%);
|
|
548
|
+
--calendar-status-accepted-border: #065f46;
|
|
549
|
+
--calendar-status-tentative-text: #fbbf24;
|
|
550
|
+
--calendar-status-tentative-background: rgb(69 26 3 / 80%);
|
|
551
|
+
--calendar-status-tentative-border: #92400e;
|
|
552
|
+
--calendar-status-declined-text: #fb7185;
|
|
553
|
+
--calendar-status-declined-background: rgb(76 5 25 / 80%);
|
|
554
|
+
--calendar-status-declined-border: #9f1239;
|
|
555
|
+
--calendar-status-needsAction-text: #38bdf8;
|
|
556
|
+
--calendar-status-needsAction-background: rgb(8 47 73 / 80%);
|
|
557
|
+
--calendar-status-needsAction-border: #075985;
|
|
430
558
|
}
|
|
431
559
|
|
|
432
560
|
@media (prefers-color-scheme: dark) {
|
|
@@ -470,6 +598,32 @@
|
|
|
470
598
|
--calendar-input-border-style: solid;
|
|
471
599
|
--calendar-option-hover: #2a2a2a;
|
|
472
600
|
--calendar-option-active: #10262b;
|
|
601
|
+
--calendar-chip-group-background: rgb(30 41 59 / 90%);
|
|
602
|
+
--calendar-chip-group-border: rgb(51 65 85 / 60%);
|
|
603
|
+
--calendar-chip-active-background: #334155;
|
|
604
|
+
--calendar-chip-badge-border: #1e293b;
|
|
605
|
+
--calendar-today-background: #1e293b;
|
|
606
|
+
--calendar-today-hover: #334155;
|
|
607
|
+
--calendar-today-text: #e2e8f0;
|
|
608
|
+
--calendar-card-background: rgb(30 41 59 / 80%);
|
|
609
|
+
--calendar-card-border: #334155;
|
|
610
|
+
--calendar-card-title: #ffffff;
|
|
611
|
+
--calendar-card-time: #94a3b8;
|
|
612
|
+
--calendar-card-active-background: rgb(30 27 75 / 80%);
|
|
613
|
+
--calendar-card-active-border: #3730a3;
|
|
614
|
+
--calendar-card-active-ring: rgb(129 140 248 / 30%);
|
|
615
|
+
--calendar-status-accepted-text: #34d399;
|
|
616
|
+
--calendar-status-accepted-background: rgb(2 44 34 / 80%);
|
|
617
|
+
--calendar-status-accepted-border: #065f46;
|
|
618
|
+
--calendar-status-tentative-text: #fbbf24;
|
|
619
|
+
--calendar-status-tentative-background: rgb(69 26 3 / 80%);
|
|
620
|
+
--calendar-status-tentative-border: #92400e;
|
|
621
|
+
--calendar-status-declined-text: #fb7185;
|
|
622
|
+
--calendar-status-declined-background: rgb(76 5 25 / 80%);
|
|
623
|
+
--calendar-status-declined-border: #9f1239;
|
|
624
|
+
--calendar-status-needsAction-text: #38bdf8;
|
|
625
|
+
--calendar-status-needsAction-background: rgb(8 47 73 / 80%);
|
|
626
|
+
--calendar-status-needsAction-border: #075985;
|
|
473
627
|
}
|
|
474
628
|
}
|
|
475
629
|
|