@open-webapp/calendar 0.2.1 → 0.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 +26 -0
- package/dist/react/CalendarPane.js +45 -20
- package/dist/react/CalendarSelector.d.ts +6 -0
- package/dist/react/CalendarSelector.js +22 -11
- package/dist/styles.css +24 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -158,6 +158,14 @@ Call at top level of a vitest file; it registers a `describe('StorageAdapter con
|
|
|
158
158
|
| `onClose?` | `() => void` | |
|
|
159
159
|
| `popupName?` | `string` | `window.open` name for the event popup (default `notesdiary-cal-event`) |
|
|
160
160
|
|
|
161
|
+
#### RSVP chips
|
|
162
|
+
|
|
163
|
+
The chips appear in order: Accepted, Maybe, Declined, Unaccepted. Accepted, Maybe, and Declined toggle individual statuses; an empty selection falls back to `DEFAULT_RSVP_STATUSES` (accepted, tentative, needsAction).
|
|
164
|
+
|
|
165
|
+
Unaccepted is an exclusive shortcut: clicking it selects only `needsAction`, saving the previous selection locally in the pane. Clicking it again restores that selection, or the defaults when no saved selection is available. Toggling another chip while exclusive mode is active applies that toggle to the saved selection (or defaults). An external nonexclusive selection clears the saved selection. Unaccepted is pressed only when the selection is exactly `needsAction`; a mixed selection containing `needsAction` does not press it.
|
|
166
|
+
|
|
167
|
+
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.
|
|
168
|
+
|
|
161
169
|
Built-in copy (overridable only through the slots above):
|
|
162
170
|
|
|
163
171
|
- Not connected: "Not connected to Google Calendar." with "Connect Google Calendar".
|
|
@@ -166,10 +174,16 @@ Built-in copy (overridable only through the slots above):
|
|
|
166
174
|
- Loading: "Loading calendar…".
|
|
167
175
|
- Empty: "No matching events in the next 30 days".
|
|
168
176
|
|
|
177
|
+
#### Footer calendar selection
|
|
178
|
+
|
|
179
|
+
One footer combobox opens a shared searchable dropdown, with calendars grouped under account headings in multi-source use. Each account has its own Clear (primary, else first calendar) and filtered Select all, which adds matching calendars while retaining existing selections. Actions affect only their account; its last selected calendar cannot be deselected, including a non-primary selection.
|
|
180
|
+
|
|
169
181
|
### CalendarSelector props
|
|
170
182
|
|
|
171
183
|
`calendars: CalendarInfo[]`, `selectedIds: string[]`, `onChange(ids)`, `heading?: string` (account heading in multi-source use). It has no slot props or `popupName`. At least one calendar stays selected (deselecting the last one is ignored). "Clear" resets to the primary calendar. Built-in copy: "Search calendars", "Select all", "Clear", "Done", "No calendars found", "No calendars available".
|
|
172
184
|
|
|
185
|
+
The exported standalone selector keeps its single-list props and behavior; Select all adds only search matches while retaining existing selections.
|
|
186
|
+
|
|
173
187
|
### Host CSS
|
|
174
188
|
|
|
175
189
|
`styles.css` is self-contained (plain colors, dark mode via `prefers-color-scheme`). It requires no host CSS variables. Variables it reads, all with defaults:
|
|
@@ -178,6 +192,18 @@ Built-in copy (overridable only through the slots above):
|
|
|
178
192
|
- `--chip-row-height` (fallback `44px`; host may set it to match its chip row for sticky headers)
|
|
179
193
|
- `--rsvp-bar` (set internally per RSVP status; do not override)
|
|
180
194
|
|
|
195
|
+
The Unaccepted count badge uses `.calendar-rsvp-chip-badge`, with light and dark styling included in `styles.css`.
|
|
196
|
+
|
|
197
|
+
## 0.4.0 (BREAKING pre-1.0 minor)
|
|
198
|
+
|
|
199
|
+
- CalendarPane replaces per-account footer triggers with one shared dropdown grouped by account. Hosts relying on multiple footer triggers must adapt. Account-local selection rules and the exported standalone CalendarSelector remain compatible.
|
|
200
|
+
|
|
201
|
+
## 0.3.0 (minor, additive)
|
|
202
|
+
|
|
203
|
+
- Adds the Unaccepted exclusive shortcut after Accepted, Maybe, and Declined, with local selection restoration and default fallback as described above.
|
|
204
|
+
- 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`.
|
|
205
|
+
- Adds the `.calendar-rsvp-chip-badge` host styling hook. Public props and default RSVP statuses remain compatible.
|
|
206
|
+
|
|
181
207
|
## 0.2.1 (patch)
|
|
182
208
|
|
|
183
209
|
Fixes only, no behavior contract change: `getState().loading` is now published while a sync pass is in flight; a selection-change (partial) pass no longer clears `deferred` set by an earlier full pass; `CalendarPane` tolerates controllers without `getState`/`hydrate`.
|
|
@@ -1,7 +1,17 @@
|
|
|
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
3
|
import { filterByRsvp, groupByDay, getOrganizerLabel, formatDayHeader, isEventCurrent, findNextUpcomingEvent, getCalendarEventKey, DEFAULT_RSVP_STATUSES, } from '../core/events.js';
|
|
4
|
-
import {
|
|
4
|
+
import { GroupedCalendarSelector } from './CalendarSelector.js';
|
|
5
|
+
function toggleRsvpStatus(selection, status) {
|
|
6
|
+
const next = new Set(selection);
|
|
7
|
+
if (next.has(status)) {
|
|
8
|
+
next.delete(status);
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
next.add(status);
|
|
12
|
+
}
|
|
13
|
+
return next.size === 0 ? new Set(DEFAULT_RSVP_STATUSES) : next;
|
|
14
|
+
}
|
|
5
15
|
function useWindowWidth() {
|
|
6
16
|
const [width, setWidth] = useState(typeof window !== 'undefined' ? window.innerWidth : 1024);
|
|
7
17
|
useEffect(() => {
|
|
@@ -23,16 +33,16 @@ const RSVP_CHIPS = [
|
|
|
23
33
|
label: 'Maybe',
|
|
24
34
|
icon: (_jsxs("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [_jsx("path", { d: "M5.2 5.6C5.2 4.2 6.2 3.2 7.5 3.2C8.8 3.2 9.8 4.2 9.8 5.4C9.8 6.8 7.6 6.9 7.6 8.8", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("circle", { cx: "7.6", cy: "11.2", r: "0.9", fill: "currentColor" })] })),
|
|
25
35
|
},
|
|
26
|
-
{
|
|
27
|
-
status: 'needsAction',
|
|
28
|
-
label: 'Unaccepted',
|
|
29
|
-
icon: (_jsx("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: _jsx("circle", { cx: "7.5", cy: "7.5", r: "4.3", stroke: "currentColor", strokeWidth: "1.4" }) })),
|
|
30
|
-
},
|
|
31
36
|
{
|
|
32
37
|
status: 'declined',
|
|
33
38
|
label: 'Declined',
|
|
34
39
|
icon: (_jsx("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: _jsx("path", { d: "M4 4L11 11M11 4L4 11", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) })),
|
|
35
40
|
},
|
|
41
|
+
{
|
|
42
|
+
status: 'needsAction',
|
|
43
|
+
label: 'Unaccepted',
|
|
44
|
+
icon: (_jsx("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: _jsx("circle", { cx: "7.5", cy: "7.5", r: "4.3", stroke: "currentColor", strokeWidth: "1.4" }) })),
|
|
45
|
+
},
|
|
36
46
|
];
|
|
37
47
|
const DEFAULT_POPUP_NAME = 'notesdiary-cal-event';
|
|
38
48
|
const POPUP_W = 560;
|
|
@@ -122,6 +132,13 @@ export function CalendarPane(props) {
|
|
|
122
132
|
const chipsRef = useRef(null);
|
|
123
133
|
const popupRef = useRef(null);
|
|
124
134
|
const [now, setNow] = useState(() => new Date());
|
|
135
|
+
const [savedSelection, setSavedSelection] = useState(null);
|
|
136
|
+
const isExclusive = selectedRsvpStatuses.size === 1 && selectedRsvpStatuses.has('needsAction');
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (!isExclusive && savedSelection !== null)
|
|
139
|
+
setSavedSelection(null);
|
|
140
|
+
}, [isExclusive, savedSelection]);
|
|
141
|
+
const unacceptedCount = useMemo(() => events.filter((event) => event.selfResponseStatus === 'needsAction').length, [events]);
|
|
125
142
|
const filteredEvents = useMemo(() => filterByRsvp(events, selectedRsvpStatuses), [events, selectedRsvpStatuses]);
|
|
126
143
|
const calendarsByKey = useMemo(() => {
|
|
127
144
|
const map = new Map();
|
|
@@ -143,19 +160,21 @@ export function CalendarPane(props) {
|
|
|
143
160
|
const nextUpcomingEvent = findNextUpcomingEvent(filteredEvents, now);
|
|
144
161
|
const selectedEventKey = currentEventKeys.values().next().value ??
|
|
145
162
|
(nextUpcomingEvent ? getCalendarEventKey(nextUpcomingEvent) : undefined);
|
|
146
|
-
function
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (next.size === 0) {
|
|
151
|
-
props.onRsvpStatusesChange(new Set(DEFAULT_RSVP_STATUSES));
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
163
|
+
function handleUnaccepted() {
|
|
164
|
+
if (isExclusive) {
|
|
165
|
+
props.onRsvpStatusesChange(savedSelection ?? new Set(DEFAULT_RSVP_STATUSES));
|
|
166
|
+
setSavedSelection(null);
|
|
154
167
|
}
|
|
155
168
|
else {
|
|
156
|
-
|
|
169
|
+
setSavedSelection(new Set(selectedRsvpStatuses));
|
|
170
|
+
props.onRsvpStatusesChange(new Set(['needsAction']));
|
|
157
171
|
}
|
|
158
|
-
|
|
172
|
+
}
|
|
173
|
+
function handleToggle(status) {
|
|
174
|
+
const base = isExclusive ? savedSelection ?? DEFAULT_RSVP_STATUSES : selectedRsvpStatuses;
|
|
175
|
+
if (isExclusive)
|
|
176
|
+
setSavedSelection(null);
|
|
177
|
+
props.onRsvpStatusesChange(toggleRsvpStatus(base, status));
|
|
159
178
|
}
|
|
160
179
|
const dayGroups = groupByDay(filteredEvents, todayIso);
|
|
161
180
|
function scrollToToday(behavior = 'auto') {
|
|
@@ -279,7 +298,7 @@ export function CalendarPane(props) {
|
|
|
279
298
|
const loading = ctrlState.loading && filteredEvents.length === 0;
|
|
280
299
|
const emptyStateText = 'No matching events in the next 30 days';
|
|
281
300
|
const showCalendarSelector = calendarSources.length > 0;
|
|
282
|
-
return (_jsxs("div", { className: "calendar-pane", ref: paneRef, children: [_jsxs("div", { className: "calendar-pane-scroll", children: [_jsxs("div", { className: "calendar-rsvp-chips", ref: chipsRef, children: [_jsx("button", { type: "button", className: "calendar-rsvp-chip calendar-scroll-today-btn", "aria-label": "Scroll to today", title: "Scroll to today", onClick: () => recenter('smooth'), children: _jsxs("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [_jsx("rect", { x: "2", y: "3", width: "11", height: "10", rx: "1.5", stroke: "currentColor", strokeWidth: "1.2" }), _jsx("path", { d: "M2 6H13", stroke: "currentColor", strokeWidth: "1.2" }), _jsx("path", { d: "M5 2V4M10 2V4", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" }), _jsx("circle", { cx: "7.5", cy: "9.5", r: "1.3", fill: "currentColor" })] }) }), RSVP_CHIPS.map((chip) => (
|
|
301
|
+
return (_jsxs("div", { className: "calendar-pane", ref: paneRef, children: [_jsxs("div", { className: "calendar-pane-scroll", children: [_jsxs("div", { className: "calendar-rsvp-chips", ref: chipsRef, children: [_jsx("button", { type: "button", className: "calendar-rsvp-chip calendar-scroll-today-btn", "aria-label": "Scroll to today", title: "Scroll to today", onClick: () => recenter('smooth'), children: _jsxs("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", children: [_jsx("rect", { x: "2", y: "3", width: "11", height: "10", rx: "1.5", stroke: "currentColor", strokeWidth: "1.2" }), _jsx("path", { d: "M2 6H13", stroke: "currentColor", strokeWidth: "1.2" }), _jsx("path", { d: "M5 2V4M10 2V4", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" }), _jsx("circle", { cx: "7.5", cy: "9.5", r: "1.3", fill: "currentColor" })] }) }), RSVP_CHIPS.map((chip) => (_jsxs("button", { type: "button", className: `calendar-rsvp-chip${(chip.status === 'needsAction' ? isExclusive : selectedRsvpStatuses.has(chip.status)) ? ' selected' : ''}`, "aria-pressed": chip.status === 'needsAction' ? isExclusive : selectedRsvpStatuses.has(chip.status), "aria-label": chip.status === 'needsAction' ? `${chip.label} (${unacceptedCount})` : chip.label, title: chip.status === 'needsAction' ? `${chip.label} (${unacceptedCount})` : chip.label, onClick: () => chip.status === 'needsAction' ? handleUnaccepted() : handleToggle(chip.status), children: [chip.icon, chip.status === 'needsAction' && (_jsx("span", { className: "calendar-rsvp-chip-badge", "aria-hidden": "true", children: unacceptedCount > 99 ? '99+' : unacceptedCount }))] }, chip.status)))] }), 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) => {
|
|
283
302
|
if (el) {
|
|
284
303
|
dayHeaderRefs.current.set(group.dateKey, el);
|
|
285
304
|
}
|
|
@@ -324,7 +343,13 @@ export function CalendarPane(props) {
|
|
|
324
343
|
openEventPopup(event.htmlLink, popupRef, popupName);
|
|
325
344
|
}
|
|
326
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));
|
|
327
|
-
})] }, 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:
|
|
328
|
-
|
|
329
|
-
|
|
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, { groups: calendarSources.map((s) => ({
|
|
347
|
+
id: s.id,
|
|
348
|
+
calendars: controller.getCalendars(s.id),
|
|
349
|
+
selectedIds: controller.getSelectedIds(s.id),
|
|
350
|
+
onChange: (ids) => {
|
|
351
|
+
void controller.setSelectedIds(s.id, ids);
|
|
352
|
+
},
|
|
353
|
+
heading: multi ? s.email : undefined,
|
|
354
|
+
})) }) }))] }));
|
|
330
355
|
}
|
|
@@ -7,4 +7,10 @@ interface CalendarSelectorProps {
|
|
|
7
7
|
heading?: string;
|
|
8
8
|
}
|
|
9
9
|
export declare function CalendarSelector({ calendars, selectedIds, onChange, heading }: CalendarSelectorProps): import("react").JSX.Element;
|
|
10
|
+
/** Internal pane renderer; intentionally not re-exported from the package barrel. */
|
|
11
|
+
export declare function GroupedCalendarSelector({ groups }: {
|
|
12
|
+
groups: (CalendarSelectorProps & {
|
|
13
|
+
id: string;
|
|
14
|
+
})[];
|
|
15
|
+
}): import("react").JSX.Element;
|
|
10
16
|
export {};
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useId, useRef, useState } from 'react';
|
|
3
3
|
import { filterCalendarsByName, summarizeCalendarSelection } from '../core/events.js';
|
|
4
4
|
export function CalendarSelector({ calendars, selectedIds, onChange, heading }) {
|
|
5
|
+
return _jsx(GroupedCalendarSelector, { groups: [{ id: '', calendars, selectedIds, onChange, heading }] });
|
|
6
|
+
}
|
|
7
|
+
/** Internal pane renderer; intentionally not re-exported from the package barrel. */
|
|
8
|
+
export function GroupedCalendarSelector({ groups }) {
|
|
5
9
|
const [open, setOpen] = useState(false);
|
|
6
10
|
const [query, setQuery] = useState('');
|
|
7
11
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
@@ -9,8 +13,9 @@ export function CalendarSelector({ calendars, selectedIds, onChange, heading })
|
|
|
9
13
|
const searchRef = useRef(null);
|
|
10
14
|
const rootRef = useRef(null);
|
|
11
15
|
const listboxId = useId();
|
|
12
|
-
const
|
|
13
|
-
const
|
|
16
|
+
const visibleGroups = groups.map((group) => ({ ...group, visible: filterCalendarsByName(group.calendars, query) }));
|
|
17
|
+
const visibleCalendars = visibleGroups.flatMap((group) => group.visible.map((calendar) => ({ calendar, group })));
|
|
18
|
+
const optionId = (sourceId, calendarId) => `${listboxId}-${encodeURIComponent(JSON.stringify([sourceId, calendarId]))}`;
|
|
14
19
|
useEffect(() => {
|
|
15
20
|
if (open)
|
|
16
21
|
searchRef.current?.focus();
|
|
@@ -37,7 +42,8 @@ export function CalendarSelector({ calendars, selectedIds, onChange, heading })
|
|
|
37
42
|
setOpen(false);
|
|
38
43
|
buttonRef.current?.focus();
|
|
39
44
|
}
|
|
40
|
-
function toggle(id) {
|
|
45
|
+
function toggle(group, id) {
|
|
46
|
+
const { selectedIds, onChange } = group;
|
|
41
47
|
if (selectedIds.includes(id)) {
|
|
42
48
|
if (selectedIds.length === 1)
|
|
43
49
|
return;
|
|
@@ -46,11 +52,13 @@ export function CalendarSelector({ calendars, selectedIds, onChange, heading })
|
|
|
46
52
|
}
|
|
47
53
|
onChange([...selectedIds, id]);
|
|
48
54
|
}
|
|
49
|
-
function selectAll() {
|
|
50
|
-
const
|
|
55
|
+
function selectAll(group) {
|
|
56
|
+
const { selectedIds, onChange } = group;
|
|
57
|
+
const visibleIds = group.visible.map((calendar) => calendar.id);
|
|
51
58
|
onChange([...selectedIds, ...visibleIds.filter((id) => !selectedIds.includes(id))]);
|
|
52
59
|
}
|
|
53
|
-
function clear() {
|
|
60
|
+
function clear({ calendars, onChange }) {
|
|
61
|
+
const primaryId = calendars.find((calendar) => calendar.primary)?.id ?? calendars[0]?.id;
|
|
54
62
|
onChange(primaryId ? [primaryId] : []);
|
|
55
63
|
}
|
|
56
64
|
function handleListboxKeyDown(event) {
|
|
@@ -75,16 +83,19 @@ export function CalendarSelector({ calendars, selectedIds, onChange, heading })
|
|
|
75
83
|
event.preventDefault();
|
|
76
84
|
const activeCalendar = visibleCalendars[activeIndex];
|
|
77
85
|
if (activeCalendar)
|
|
78
|
-
toggle(activeCalendar.id);
|
|
86
|
+
toggle(activeCalendar.group, activeCalendar.calendar.id);
|
|
79
87
|
}
|
|
80
88
|
}
|
|
81
|
-
const label =
|
|
89
|
+
const label = groups.length === 1
|
|
90
|
+
? summarizeCalendarSelection(groups[0].selectedIds, groups[0].calendars)
|
|
91
|
+
: `${groups.reduce((count, group) => count + group.selectedIds.length, 0)} calendars selected`;
|
|
92
|
+
const activeCalendar = visibleCalendars[activeIndex];
|
|
82
93
|
return (_jsxs("div", { className: "calendar-selector", ref: rootRef, children: [_jsx("button", { ref: buttonRef, type: "button", role: "combobox", "aria-label": label, "aria-expanded": open, "aria-controls": listboxId, "aria-haspopup": "listbox", onClick: () => {
|
|
83
94
|
setOpen((isOpen) => !isOpen);
|
|
84
95
|
setQuery('');
|
|
85
96
|
setActiveIndex(0);
|
|
86
|
-
}, children: label }), open && (_jsxs("div", { className: "calendar-selector-dropdown calendar-selector-dropdown--upward", onKeyDown: handleListboxKeyDown, children: [heading && (_jsx("div", { role: "heading", "aria-level": 3, className: "calendar-selector-heading", children: heading })), _jsx("input", { ref: searchRef, type: "search", role: "searchbox", "aria-label": "Search calendars", value: query, onChange: (event) => {
|
|
97
|
+
}, children: label }), open && (_jsxs("div", { className: "calendar-selector-dropdown calendar-selector-dropdown--upward", onKeyDown: handleListboxKeyDown, children: [groups.length === 1 && groups[0].heading && (_jsx("div", { role: "heading", "aria-level": 3, className: "calendar-selector-heading", children: groups[0].heading })), _jsx("input", { ref: searchRef, type: "search", role: "searchbox", "aria-label": "Search calendars", value: query, onChange: (event) => {
|
|
87
98
|
setQuery(event.target.value);
|
|
88
99
|
setActiveIndex(0);
|
|
89
|
-
} }), _jsxs("div", { className: "calendar-selector-actions", children: [_jsx("button", { type: "button", onClick: selectAll, children: "Select all" }), _jsx("button", { type: "button", onClick: clear, children: "Clear" }), _jsx("button", { type: "button", onClick: close, children: "Done" })] }), _jsx("div", { id: listboxId, role: "listbox", "aria-multiselectable": "true", "aria-activedescendant":
|
|
100
|
+
} }), _jsxs("div", { className: "calendar-selector-actions", children: [groups.length === 1 && _jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: () => selectAll(visibleGroups[0]), children: "Select all" }), _jsx("button", { type: "button", onClick: () => clear(groups[0]), children: "Clear" })] }), _jsx("button", { type: "button", onClick: close, children: "Done" })] }), _jsx("div", { id: listboxId, role: "listbox", "aria-multiselectable": "true", "aria-activedescendant": activeCalendar ? optionId(activeCalendar.group.id, activeCalendar.calendar.id) : undefined, tabIndex: -1, children: visibleGroups.map((group) => (_jsxs("div", { role: groups.length > 1 ? 'group' : undefined, "aria-label": group.heading, children: [groups.length > 1 && _jsxs(_Fragment, { children: [_jsx("div", { role: "heading", "aria-level": 3, className: "calendar-selector-heading", children: group.heading }), _jsxs("div", { className: "calendar-selector-actions", children: [_jsx("button", { type: "button", onClick: () => selectAll(group), children: "Select all" }), _jsx("button", { type: "button", onClick: () => clear(group), children: "Clear" })] })] }), group.calendars.length === 0 ? (_jsx("p", { children: "No calendars available" })) : group.visible.length === 0 ? (_jsx("p", { children: "No calendars found" })) : (group.visible.map((calendar) => (_jsxs("button", { type: "button", id: optionId(group.id, calendar.id), role: "option", title: calendar.summary, "aria-selected": group.selectedIds.includes(calendar.id), "data-active": (activeCalendar?.group.id === group.id && activeCalendar.calendar.id === calendar.id) || undefined, onClick: () => toggle(group, calendar.id), children: [_jsx("input", { type: "checkbox", className: "calendar-selector-checkbox", checked: group.selectedIds.includes(calendar.id), readOnly: true, tabIndex: -1, "aria-hidden": "true" }), _jsx("span", { "aria-hidden": "true", className: "calendar-selector-dot", "data-testid": `calendar-color-${calendar.id}`, style: { backgroundColor: calendar.backgroundColor } }), _jsx("span", { className: "calendar-selector-name", title: calendar.summary, children: calendar.summary })] }, optionId(group.id, calendar.id)))))] }, group.id))) })] }))] }));
|
|
90
101
|
}
|
package/dist/styles.css
CHANGED
|
@@ -68,12 +68,16 @@
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
.calendar-rsvp-chip {
|
|
71
|
-
width:
|
|
71
|
+
width: auto;
|
|
72
|
+
min-width: 32px;
|
|
72
73
|
height: 32px;
|
|
74
|
+
box-sizing: border-box;
|
|
75
|
+
flex-shrink: 0;
|
|
73
76
|
display: flex;
|
|
74
77
|
align-items: center;
|
|
75
78
|
justify-content: center;
|
|
76
|
-
|
|
79
|
+
gap: 4px;
|
|
80
|
+
padding: 0 8px;
|
|
77
81
|
background: transparent;
|
|
78
82
|
color: #333;
|
|
79
83
|
border: 1px solid #ccc;
|
|
@@ -97,6 +101,19 @@
|
|
|
97
101
|
border-color: #081a59;
|
|
98
102
|
}
|
|
99
103
|
|
|
104
|
+
.calendar-rsvp-chip-badge {
|
|
105
|
+
min-width: 16px;
|
|
106
|
+
padding: 1px 4px;
|
|
107
|
+
border-radius: 999px;
|
|
108
|
+
background: #e8eefb;
|
|
109
|
+
color: #081a59;
|
|
110
|
+
font-size: 11px;
|
|
111
|
+
font-weight: 600;
|
|
112
|
+
line-height: 16px;
|
|
113
|
+
text-align: center;
|
|
114
|
+
white-space: nowrap;
|
|
115
|
+
}
|
|
116
|
+
|
|
100
117
|
.calendar-empty-state {
|
|
101
118
|
padding: 24px 16px;
|
|
102
119
|
color: #999;
|
|
@@ -306,6 +323,11 @@
|
|
|
306
323
|
border-color: #22d0ef;
|
|
307
324
|
}
|
|
308
325
|
|
|
326
|
+
.calendar-rsvp-chip-badge {
|
|
327
|
+
background: #081a59;
|
|
328
|
+
color: #e8eefb;
|
|
329
|
+
}
|
|
330
|
+
|
|
309
331
|
.calendar-day-header {
|
|
310
332
|
background: #222;
|
|
311
333
|
color: #eee;
|