@open-webapp/calendar 0.4.0 → 0.5.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 +39 -2
- package/dist/react/CalendarPane.d.ts +1 -0
- package/dist/react/CalendarPane.js +5 -5
- package/dist/react/CalendarSelector.d.ts +4 -2
- package/dist/react/CalendarSelector.js +6 -6
- package/dist/styles.css +232 -183
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ void controller.refresh(); // then syncs from Google
|
|
|
38
38
|
<CalendarPane
|
|
39
39
|
sources={sources}
|
|
40
40
|
controller={controller}
|
|
41
|
+
theme="system" // optional: 'system' | 'light' | 'dark'
|
|
41
42
|
selectedRsvpStatuses={statuses} // Set<RsvpStatus>
|
|
42
43
|
onRsvpStatusesChange={setStatuses}
|
|
43
44
|
connected={connected} // optional, see CalendarPane props
|
|
@@ -146,6 +147,7 @@ Call at top level of a vitest file; it registers a `describe('StorageAdapter con
|
|
|
146
147
|
| --- | --- | --- |
|
|
147
148
|
| `sources` | `Source[]` | shown accounts; 2+ enables multi-source UI (per-source color and email) |
|
|
148
149
|
| `controller` | `CalendarController` | |
|
|
150
|
+
| `theme?` | `'system' \| 'light' \| 'dark'` | defaults to `'system'`, including when `undefined`; applies to every pane state and all internal selectors |
|
|
149
151
|
| `selectedRsvpStatuses` | `Set<RsvpStatus>` | filter; `DEFAULT_RSVP_STATUSES` = accepted, tentative, needsAction |
|
|
150
152
|
| `onRsvpStatusesChange` | `(next) => void` | |
|
|
151
153
|
| `renderEventAction?` | `(event) => ReactNode` | per-event slot |
|
|
@@ -178,15 +180,38 @@ Built-in copy (overridable only through the slots above):
|
|
|
178
180
|
|
|
179
181
|
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
182
|
|
|
183
|
+
The footer and standalone `CalendarSelector` share a padded, rounded trigger with a visible decorative chevron, themed hover surface and keyboard-only focus outline. Long labels ellipsize visually while retaining the full accessible name. This is a visual-only correction to the former native trigger geometry: click, native Enter/Space activation, search, keyboard navigation, Escape/Done focus return, combobox relationships and selection callbacks/rules are unchanged. The default footer remains 64px high, with the upward dropdown 6px above the trigger.
|
|
184
|
+
|
|
181
185
|
### CalendarSelector props
|
|
182
186
|
|
|
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".
|
|
187
|
+
`calendars: CalendarInfo[]`, `selectedIds: string[]`, `onChange(ids)`, `heading?: string` (account heading in multi-source use), `theme?: 'system' | 'light' | 'dark'` (defaults to `'system'`, including when `undefined`). 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".
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
import { CalendarSelector } from '@open-webapp/calendar';
|
|
191
|
+
import '@open-webapp/calendar/styles.css';
|
|
192
|
+
|
|
193
|
+
<CalendarSelector
|
|
194
|
+
calendars={calendars}
|
|
195
|
+
selectedIds={selectedIds}
|
|
196
|
+
onChange={setSelectedIds}
|
|
197
|
+
heading="me@example.com"
|
|
198
|
+
theme="dark"
|
|
199
|
+
/>;
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Theme behavior
|
|
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 saved RSVP selection. Theme changes do not trigger selection callbacks or controller refreshes.
|
|
205
|
+
|
|
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
|
+
|
|
208
|
+
The host owns theme-picker UI and preference persistence (for example, saving a choice and passing it back as `theme`). The package neither stores the choice nor adds it to the controller. Hosts also own styling of `emptyState`, `sourceNotice` and `renderEventAction` slot content; give custom content explicit styles where needed rather than relying on the built-in palette.
|
|
184
209
|
|
|
185
210
|
The exported standalone selector keeps its single-list props and behavior; Select all adds only search matches while retaining existing selections.
|
|
186
211
|
|
|
187
212
|
### Host CSS
|
|
188
213
|
|
|
189
|
-
`styles.css` is self-contained
|
|
214
|
+
`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:
|
|
190
215
|
|
|
191
216
|
- `--calendar-footer-height` (default `64px`, set on `.calendar-pane`; override to match host footer)
|
|
192
217
|
- `--chip-row-height` (fallback `44px`; host may set it to match its chip row for sticky headers)
|
|
@@ -194,6 +219,18 @@ The exported standalone selector keeps its single-list props and behavior; Selec
|
|
|
194
219
|
|
|
195
220
|
The Unaccepted count badge uses `.calendar-rsvp-chip-badge`, with light and dark styling included in `styles.css`.
|
|
196
221
|
|
|
222
|
+
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
|
+
|
|
224
|
+
## 0.5.1 (patch)
|
|
225
|
+
|
|
226
|
+
- 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.
|
|
227
|
+
- Approved visual-only exception to native trigger geometry/surface treatment; the default 64px footer and 6px dropdown clearance are preserved. Public props, copy, accessible names/relationships, interactions, selection semantics, controller and storage/schema contracts are unchanged. No migration is required.
|
|
228
|
+
|
|
229
|
+
## 0.5.0 (minor, additive)
|
|
230
|
+
|
|
231
|
+
- Adds optional `theme?: 'system' | 'light' | 'dark'` to both React components, with live OS tracking by default and live per-instance overrides.
|
|
232
|
+
- Themes all built-in pane states and internal/standalone selectors while preserving existing palettes, layout, copy and interaction contracts. Existing callers need no migration.
|
|
233
|
+
|
|
197
234
|
## 0.4.0 (BREAKING pre-1.0 minor)
|
|
198
235
|
|
|
199
236
|
- 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.
|
|
@@ -3,6 +3,7 @@ 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>;
|
|
5
5
|
export interface CalendarPaneProps {
|
|
6
|
+
theme?: 'system' | 'light' | 'dark';
|
|
6
7
|
sources: Source[];
|
|
7
8
|
controller: CalendarController;
|
|
8
9
|
selectedRsvpStatuses: RsvpSelection;
|
|
@@ -100,7 +100,7 @@ function usableUrl(url) {
|
|
|
100
100
|
}
|
|
101
101
|
export function CalendarPane(props) {
|
|
102
102
|
const isDesktop = useWindowWidth() >= 960;
|
|
103
|
-
const { sources, controller, selectedRsvpStatuses } = props;
|
|
103
|
+
const { sources, controller, selectedRsvpStatuses, theme = 'system' } = props;
|
|
104
104
|
const popupName = props.popupName ?? DEFAULT_POPUP_NAME;
|
|
105
105
|
const [version, setVersion] = useState(0);
|
|
106
106
|
useEffect(() => {
|
|
@@ -286,10 +286,10 @@ export function CalendarPane(props) {
|
|
|
286
286
|
return () => observer.disconnect();
|
|
287
287
|
}, [connected, needsReauth]);
|
|
288
288
|
if (!connected) {
|
|
289
|
-
return (_jsx("div", { className: "calendar-pane", 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" })] })) }) }));
|
|
289
|
+
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" })] })) }) }));
|
|
290
290
|
}
|
|
291
291
|
if (needsReauth) {
|
|
292
|
-
return (_jsx("div", { className: "calendar-pane", children: _jsx("div", { className: "calendar-pane-prompt", children: props.sourceNotice && needsScopeSources.length > 0 ? (needsScopeSources.map((s) => _jsx("div", { children: props.sourceNotice?.(s) }, s.id))) : (_jsxs(_Fragment, { children: [_jsx("p", { children: "Calendar connection needs reauthorization." }), _jsx("button", { className: "calendar-connect-button", onClick: props.onReconnect, children: "Reconnect to restore sync" })] })) }) }));
|
|
292
|
+
return (_jsx("div", { className: "calendar-pane", "data-calendar-theme": theme, children: _jsx("div", { className: "calendar-pane-prompt", children: props.sourceNotice && needsScopeSources.length > 0 ? (needsScopeSources.map((s) => _jsx("div", { children: props.sourceNotice?.(s) }, s.id))) : (_jsxs(_Fragment, { children: [_jsx("p", { children: "Calendar connection needs reauthorization." }), _jsx("button", { className: "calendar-connect-button", onClick: props.onReconnect, children: "Reconnect to restore sync" })] })) }) }));
|
|
293
293
|
}
|
|
294
294
|
const sourcesWithEvents = new Set(filteredEvents.map((e) => e.sourceId));
|
|
295
295
|
const hasErrorWithoutEvents = sources.some((s) => statuses[s.id] === 'error' && !sourcesWithEvents.has(s.id));
|
|
@@ -298,7 +298,7 @@ export function CalendarPane(props) {
|
|
|
298
298
|
const loading = ctrlState.loading && filteredEvents.length === 0;
|
|
299
299
|
const emptyStateText = 'No matching events in the next 30 days';
|
|
300
300
|
const showCalendarSelector = calendarSources.length > 0;
|
|
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) => {
|
|
301
|
+
return (_jsxs("div", { className: "calendar-pane", "data-calendar-theme": theme, 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) => {
|
|
302
302
|
if (el) {
|
|
303
303
|
dayHeaderRefs.current.set(group.dateKey, el);
|
|
304
304
|
}
|
|
@@ -343,7 +343,7 @@ export function CalendarPane(props) {
|
|
|
343
343
|
openEventPopup(event.htmlLink, popupRef, popupName);
|
|
344
344
|
}
|
|
345
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, { groups: calendarSources.map((s) => ({
|
|
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),
|
|
@@ -5,12 +5,14 @@ interface CalendarSelectorProps {
|
|
|
5
5
|
onChange: (selectedIds: string[]) => void;
|
|
6
6
|
/** Optional account heading (used when several sources are shown). */
|
|
7
7
|
heading?: string;
|
|
8
|
+
theme?: 'system' | 'light' | 'dark';
|
|
8
9
|
}
|
|
9
|
-
export declare function CalendarSelector({ calendars, selectedIds, onChange, heading }: CalendarSelectorProps): import("react").JSX.Element;
|
|
10
|
+
export declare function CalendarSelector({ calendars, selectedIds, onChange, heading, theme }: CalendarSelectorProps): import("react").JSX.Element;
|
|
10
11
|
/** Internal pane renderer; intentionally not re-exported from the package barrel. */
|
|
11
|
-
export declare function GroupedCalendarSelector({ groups }: {
|
|
12
|
+
export declare function GroupedCalendarSelector({ groups, theme }: {
|
|
12
13
|
groups: (CalendarSelectorProps & {
|
|
13
14
|
id: string;
|
|
14
15
|
})[];
|
|
16
|
+
theme?: CalendarSelectorProps['theme'];
|
|
15
17
|
}): import("react").JSX.Element;
|
|
16
18
|
export {};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useId, useRef, useState } from 'react';
|
|
3
3
|
import { filterCalendarsByName, summarizeCalendarSelection } from '../core/events.js';
|
|
4
|
-
export function CalendarSelector({ calendars, selectedIds, onChange, heading }) {
|
|
5
|
-
return _jsx(GroupedCalendarSelector, { groups: [{ id: '', calendars, selectedIds, onChange, heading }] });
|
|
4
|
+
export function CalendarSelector({ calendars, selectedIds, onChange, heading, theme = 'system' }) {
|
|
5
|
+
return _jsx(GroupedCalendarSelector, { theme: theme, groups: [{ id: '', calendars, selectedIds, onChange, heading }] });
|
|
6
6
|
}
|
|
7
7
|
/** Internal pane renderer; intentionally not re-exported from the package barrel. */
|
|
8
|
-
export function GroupedCalendarSelector({ groups }) {
|
|
8
|
+
export function GroupedCalendarSelector({ groups, theme = 'system' }) {
|
|
9
9
|
const [open, setOpen] = useState(false);
|
|
10
10
|
const [query, setQuery] = useState('');
|
|
11
11
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
@@ -90,11 +90,11 @@ export function GroupedCalendarSelector({ groups }) {
|
|
|
90
90
|
? summarizeCalendarSelection(groups[0].selectedIds, groups[0].calendars)
|
|
91
91
|
: `${groups.reduce((count, group) => count + group.selectedIds.length, 0)} calendars selected`;
|
|
92
92
|
const activeCalendar = visibleCalendars[activeIndex];
|
|
93
|
-
return (_jsxs("div", { className: "calendar-selector", ref: rootRef, children: [
|
|
93
|
+
return (_jsxs("div", { className: "calendar-selector", "data-calendar-theme": theme, ref: rootRef, children: [_jsxs("button", { className: "calendar-selector-trigger", ref: buttonRef, type: "button", role: "combobox", "aria-label": label, "aria-expanded": open, "aria-controls": listboxId, "aria-haspopup": "listbox", onClick: () => {
|
|
94
94
|
setOpen((isOpen) => !isOpen);
|
|
95
95
|
setQuery('');
|
|
96
96
|
setActiveIndex(0);
|
|
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) => {
|
|
97
|
+
}, children: [_jsx("span", { className: "calendar-selector-trigger-label", children: label }), _jsx("svg", { className: "calendar-selector-chevron", width: "12", height: "12", viewBox: "0 0 12 12", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: "M3 4.5 6 7.5 9 4.5", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) })] }), 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) => {
|
|
98
98
|
setQuery(event.target.value);
|
|
99
99
|
setActiveIndex(0);
|
|
100
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))) })] }))] }));
|
package/dist/styles.css
CHANGED
|
@@ -1,3 +1,55 @@
|
|
|
1
|
+
.calendar-pane,
|
|
2
|
+
.calendar-selector {
|
|
3
|
+
/* Reset the complete palette at every independent component boundary. */
|
|
4
|
+
--calendar-scheme: light;
|
|
5
|
+
--calendar-surface: #ffffff;
|
|
6
|
+
--calendar-text: #222;
|
|
7
|
+
--calendar-border: #eee;
|
|
8
|
+
--calendar-prompt: #555;
|
|
9
|
+
--calendar-accent: #00a9ce;
|
|
10
|
+
--calendar-hover: #f0f8fb;
|
|
11
|
+
--calendar-chip-text: #333;
|
|
12
|
+
--calendar-chip-border: #ccc;
|
|
13
|
+
--calendar-chip-selected: #081a59;
|
|
14
|
+
--calendar-chip-selected-text: #ffffff;
|
|
15
|
+
--calendar-badge: #e8eefb;
|
|
16
|
+
--calendar-badge-text: #081a59;
|
|
17
|
+
--calendar-header: #f5f5f5;
|
|
18
|
+
--calendar-allday: #fdf6e3;
|
|
19
|
+
--calendar-highlight: #f0f8fb;
|
|
20
|
+
--calendar-insert: #888;
|
|
21
|
+
--calendar-insert-hover: #222;
|
|
22
|
+
--calendar-insert-background: rgba(0, 0, 0, 0.06);
|
|
23
|
+
--calendar-focus: #1a73e8;
|
|
24
|
+
--calendar-rsvp-accepted: #2e7d32;
|
|
25
|
+
--calendar-rsvp-tentative: #d97706;
|
|
26
|
+
--calendar-rsvp-needsAction: #2563eb;
|
|
27
|
+
--calendar-rsvp-declined: #dc2626;
|
|
28
|
+
--calendar-selector-border: #ccc;
|
|
29
|
+
--calendar-selector-text: #1a1a1a;
|
|
30
|
+
--calendar-selector-shadow: 0 4px 14px rgb(0 0 0 / 15%);
|
|
31
|
+
--calendar-control-border: #767676;
|
|
32
|
+
--calendar-control-background: #efefef;
|
|
33
|
+
--calendar-input-background: #ffffff;
|
|
34
|
+
--calendar-control-text: #000000;
|
|
35
|
+
--calendar-control-width: 2px;
|
|
36
|
+
--calendar-button-border-style: outset;
|
|
37
|
+
--calendar-input-border-style: inset;
|
|
38
|
+
--calendar-option-hover: transparent;
|
|
39
|
+
--calendar-option-active: #e8f5f8;
|
|
40
|
+
--calendar-muted: #888;
|
|
41
|
+
--calendar-time: #666;
|
|
42
|
+
--calendar-empty: #999;
|
|
43
|
+
--calendar-chip-hover: #00a9ce;
|
|
44
|
+
--calendar-checkbox: #22a6c0;
|
|
45
|
+
color-scheme: var(--calendar-scheme);
|
|
46
|
+
color: var(--calendar-text);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.calendar-pane {
|
|
50
|
+
background: var(--calendar-surface);
|
|
51
|
+
}
|
|
52
|
+
|
|
1
53
|
.calendar-pane {
|
|
2
54
|
--calendar-footer-height: 64px;
|
|
3
55
|
display: flex;
|
|
@@ -23,8 +75,8 @@
|
|
|
23
75
|
box-sizing: border-box;
|
|
24
76
|
min-height: var(--calendar-footer-height);
|
|
25
77
|
padding: 12px 16px;
|
|
26
|
-
border-top: 1px solid
|
|
27
|
-
background:
|
|
78
|
+
border-top: 1px solid var(--calendar-border);
|
|
79
|
+
background: var(--calendar-surface);
|
|
28
80
|
}
|
|
29
81
|
|
|
30
82
|
.calendar-pane-prompt {
|
|
@@ -37,7 +89,7 @@
|
|
|
37
89
|
|
|
38
90
|
.calendar-pane-prompt p {
|
|
39
91
|
margin: 0;
|
|
40
|
-
color:
|
|
92
|
+
color: var(--calendar-prompt);
|
|
41
93
|
font-size: 14px;
|
|
42
94
|
}
|
|
43
95
|
|
|
@@ -45,15 +97,15 @@
|
|
|
45
97
|
padding: 10px 16px;
|
|
46
98
|
font-size: 14px;
|
|
47
99
|
font-weight: 500;
|
|
48
|
-
background:
|
|
49
|
-
color:
|
|
50
|
-
border: 1px solid
|
|
100
|
+
background: var(--calendar-surface);
|
|
101
|
+
color: var(--calendar-accent);
|
|
102
|
+
border: 1px solid var(--calendar-accent);
|
|
51
103
|
border-radius: 6px;
|
|
52
104
|
cursor: pointer;
|
|
53
105
|
}
|
|
54
106
|
|
|
55
107
|
.calendar-connect-button:hover {
|
|
56
|
-
background:
|
|
108
|
+
background: var(--calendar-hover);
|
|
57
109
|
}
|
|
58
110
|
|
|
59
111
|
.calendar-rsvp-chips {
|
|
@@ -63,7 +115,7 @@
|
|
|
63
115
|
flex-wrap: wrap;
|
|
64
116
|
position: sticky;
|
|
65
117
|
top: 0;
|
|
66
|
-
background:
|
|
118
|
+
background: var(--calendar-surface);
|
|
67
119
|
z-index: 2;
|
|
68
120
|
}
|
|
69
121
|
|
|
@@ -79,8 +131,8 @@
|
|
|
79
131
|
gap: 4px;
|
|
80
132
|
padding: 0 8px;
|
|
81
133
|
background: transparent;
|
|
82
|
-
color:
|
|
83
|
-
border: 1px solid
|
|
134
|
+
color: var(--calendar-chip-text);
|
|
135
|
+
border: 1px solid var(--calendar-chip-border);
|
|
84
136
|
border-radius: 999px;
|
|
85
137
|
cursor: pointer;
|
|
86
138
|
}
|
|
@@ -88,25 +140,25 @@
|
|
|
88
140
|
.calendar-scroll-today-btn {
|
|
89
141
|
margin-right: 4px;
|
|
90
142
|
padding-right: 4px;
|
|
91
|
-
border-right: 1px solid
|
|
143
|
+
border-right: 1px solid var(--calendar-chip-border);
|
|
92
144
|
}
|
|
93
145
|
|
|
94
146
|
.calendar-rsvp-chip:hover {
|
|
95
|
-
border-color:
|
|
147
|
+
border-color: var(--calendar-chip-hover);
|
|
96
148
|
}
|
|
97
149
|
|
|
98
150
|
.calendar-rsvp-chip.selected {
|
|
99
|
-
background:
|
|
100
|
-
color:
|
|
101
|
-
border-color:
|
|
151
|
+
background: var(--calendar-chip-selected);
|
|
152
|
+
color: var(--calendar-chip-selected-text);
|
|
153
|
+
border-color: var(--calendar-chip-selected);
|
|
102
154
|
}
|
|
103
155
|
|
|
104
156
|
.calendar-rsvp-chip-badge {
|
|
105
157
|
min-width: 16px;
|
|
106
158
|
padding: 1px 4px;
|
|
107
159
|
border-radius: 999px;
|
|
108
|
-
background:
|
|
109
|
-
color:
|
|
160
|
+
background: var(--calendar-badge);
|
|
161
|
+
color: var(--calendar-badge-text);
|
|
110
162
|
font-size: 11px;
|
|
111
163
|
font-weight: 600;
|
|
112
164
|
line-height: 16px;
|
|
@@ -116,7 +168,7 @@
|
|
|
116
168
|
|
|
117
169
|
.calendar-empty-state {
|
|
118
170
|
padding: 24px 16px;
|
|
119
|
-
color:
|
|
171
|
+
color: var(--calendar-empty);
|
|
120
172
|
font-size: 14px;
|
|
121
173
|
}
|
|
122
174
|
|
|
@@ -133,8 +185,8 @@
|
|
|
133
185
|
.calendar-day-header {
|
|
134
186
|
position: sticky;
|
|
135
187
|
top: var(--chip-row-height, 44px);
|
|
136
|
-
background:
|
|
137
|
-
color:
|
|
188
|
+
background: var(--calendar-header);
|
|
189
|
+
color: var(--calendar-chip-text);
|
|
138
190
|
font-size: 13px;
|
|
139
191
|
font-weight: 600;
|
|
140
192
|
padding: 8px 16px;
|
|
@@ -147,7 +199,7 @@
|
|
|
147
199
|
gap: 8px;
|
|
148
200
|
padding: 10px 16px;
|
|
149
201
|
border-left: 3px solid var(--rsvp-bar, transparent);
|
|
150
|
-
border-bottom: 1px solid
|
|
202
|
+
border-bottom: 1px solid var(--calendar-border);
|
|
151
203
|
}
|
|
152
204
|
|
|
153
205
|
.calendar-event-row {
|
|
@@ -156,28 +208,28 @@
|
|
|
156
208
|
gap: 8px;
|
|
157
209
|
padding: 10px 16px;
|
|
158
210
|
border-left: 3px solid var(--rsvp-bar, transparent);
|
|
159
|
-
border-bottom: 1px solid
|
|
211
|
+
border-bottom: 1px solid var(--calendar-border);
|
|
160
212
|
}
|
|
161
213
|
|
|
162
214
|
.calendar-event-allday {
|
|
163
|
-
background:
|
|
215
|
+
background: var(--calendar-allday);
|
|
164
216
|
font-style: italic;
|
|
165
217
|
}
|
|
166
218
|
|
|
167
219
|
.calendar-event-row--rsvp-accepted {
|
|
168
|
-
--rsvp-bar:
|
|
220
|
+
--rsvp-bar: var(--calendar-rsvp-accepted);
|
|
169
221
|
}
|
|
170
222
|
|
|
171
223
|
.calendar-event-row--rsvp-tentative {
|
|
172
|
-
--rsvp-bar:
|
|
224
|
+
--rsvp-bar: var(--calendar-rsvp-tentative);
|
|
173
225
|
}
|
|
174
226
|
|
|
175
227
|
.calendar-event-row--rsvp-needsAction {
|
|
176
|
-
--rsvp-bar:
|
|
228
|
+
--rsvp-bar: var(--calendar-rsvp-needsAction);
|
|
177
229
|
}
|
|
178
230
|
|
|
179
231
|
.calendar-event-row--rsvp-declined {
|
|
180
|
-
--rsvp-bar:
|
|
232
|
+
--rsvp-bar: var(--calendar-rsvp-declined);
|
|
181
233
|
}
|
|
182
234
|
|
|
183
235
|
.calendar-event-header {
|
|
@@ -217,36 +269,36 @@
|
|
|
217
269
|
border: none;
|
|
218
270
|
border-radius: 4px;
|
|
219
271
|
background: transparent;
|
|
220
|
-
color:
|
|
272
|
+
color: var(--calendar-insert);
|
|
221
273
|
cursor: pointer;
|
|
222
274
|
}
|
|
223
275
|
|
|
224
276
|
.calendar-event-insert-link:hover {
|
|
225
|
-
color:
|
|
226
|
-
background:
|
|
277
|
+
color: var(--calendar-insert-hover);
|
|
278
|
+
background: var(--calendar-insert-background);
|
|
227
279
|
}
|
|
228
280
|
|
|
229
281
|
.calendar-event-insert-link:focus-visible {
|
|
230
|
-
outline: 2px solid
|
|
282
|
+
outline: 2px solid var(--calendar-focus);
|
|
231
283
|
outline-offset: 1px;
|
|
232
|
-
color:
|
|
284
|
+
color: var(--calendar-insert-hover);
|
|
233
285
|
}
|
|
234
286
|
|
|
235
287
|
.calendar-event-time {
|
|
236
288
|
font-size: 12px;
|
|
237
|
-
color:
|
|
289
|
+
color: var(--calendar-time);
|
|
238
290
|
min-width: 110px;
|
|
239
291
|
flex-shrink: 0;
|
|
240
292
|
}
|
|
241
293
|
|
|
242
294
|
.calendar-event-title {
|
|
243
295
|
font-size: 14px;
|
|
244
|
-
color:
|
|
296
|
+
color: var(--calendar-text);
|
|
245
297
|
}
|
|
246
298
|
|
|
247
299
|
.calendar-event-organizer {
|
|
248
300
|
font-size: 12px;
|
|
249
|
-
color:
|
|
301
|
+
color: var(--calendar-muted);
|
|
250
302
|
}
|
|
251
303
|
|
|
252
304
|
.calendar-event-calendar {
|
|
@@ -254,7 +306,7 @@
|
|
|
254
306
|
align-items: center;
|
|
255
307
|
gap: 5px;
|
|
256
308
|
font-size: 12px;
|
|
257
|
-
color:
|
|
309
|
+
color: var(--calendar-muted);
|
|
258
310
|
}
|
|
259
311
|
|
|
260
312
|
.calendar-event-calendar-dot {
|
|
@@ -267,135 +319,153 @@
|
|
|
267
319
|
.calendar-event-join {
|
|
268
320
|
font-size: 12px;
|
|
269
321
|
font-weight: 500;
|
|
270
|
-
color:
|
|
322
|
+
color: var(--calendar-accent);
|
|
271
323
|
cursor: pointer;
|
|
272
324
|
text-decoration: none;
|
|
273
325
|
padding: 4px 8px;
|
|
274
|
-
border: 1px solid
|
|
326
|
+
border: 1px solid var(--calendar-accent);
|
|
275
327
|
border-radius: 4px;
|
|
276
328
|
}
|
|
277
329
|
|
|
278
330
|
.calendar-event-join:hover {
|
|
279
|
-
background:
|
|
331
|
+
background: var(--calendar-hover);
|
|
280
332
|
}
|
|
281
333
|
|
|
282
334
|
.calendar-event-row--current,
|
|
283
335
|
.calendar-event-row--selected {
|
|
284
|
-
background:
|
|
336
|
+
background: var(--calendar-highlight);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/* Explicit dark and system-dark use exactly the same palette. The media query
|
|
340
|
+
only assigns tokens on system roots, never on their descendants. */
|
|
341
|
+
.calendar-pane[data-calendar-theme='dark'],
|
|
342
|
+
.calendar-selector[data-calendar-theme='dark'] {
|
|
343
|
+
--calendar-scheme: dark;
|
|
344
|
+
--calendar-surface: #1a1a1a;
|
|
345
|
+
--calendar-text: #eee;
|
|
346
|
+
--calendar-border: #333;
|
|
347
|
+
--calendar-prompt: #ccc;
|
|
348
|
+
--calendar-accent: #22d0ef;
|
|
349
|
+
--calendar-hover: #2a2a2a;
|
|
350
|
+
--calendar-chip-text: #eee;
|
|
351
|
+
--calendar-chip-border: #444;
|
|
352
|
+
--calendar-chip-selected: #22d0ef;
|
|
353
|
+
--calendar-chip-selected-text: #081a59;
|
|
354
|
+
--calendar-badge: #081a59;
|
|
355
|
+
--calendar-badge-text: #e8eefb;
|
|
356
|
+
--calendar-header: #222;
|
|
357
|
+
--calendar-allday: #2a2510;
|
|
358
|
+
--calendar-highlight: #10262b;
|
|
359
|
+
--calendar-insert: #999;
|
|
360
|
+
--calendar-insert-hover: #eee;
|
|
361
|
+
--calendar-insert-background: rgba(255, 255, 255, 0.1);
|
|
362
|
+
--calendar-focus: #22d0ef;
|
|
363
|
+
--calendar-rsvp-accepted: #4ade80;
|
|
364
|
+
--calendar-rsvp-tentative: #fbbf24;
|
|
365
|
+
--calendar-rsvp-needsAction: #60a5fa;
|
|
366
|
+
--calendar-rsvp-declined: #f87171;
|
|
367
|
+
--calendar-selector-border: #333;
|
|
368
|
+
--calendar-selector-text: #eee;
|
|
369
|
+
--calendar-selector-shadow: 0 4px 14px rgb(0 0 0 / 50%);
|
|
370
|
+
--calendar-control-border: #444;
|
|
371
|
+
--calendar-control-background: #2a2a2a;
|
|
372
|
+
--calendar-input-background: #2a2a2a;
|
|
373
|
+
--calendar-control-text: #eee;
|
|
374
|
+
--calendar-control-width: 1px;
|
|
375
|
+
--calendar-button-border-style: solid;
|
|
376
|
+
--calendar-input-border-style: solid;
|
|
377
|
+
--calendar-option-hover: #2a2a2a;
|
|
378
|
+
--calendar-option-active: #10262b;
|
|
285
379
|
}
|
|
286
380
|
|
|
287
381
|
@media (prefers-color-scheme: dark) {
|
|
288
|
-
.calendar-pane-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
background:
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
border-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
.calendar-rsvp-chip-badge {
|
|
327
|
-
background: #081a59;
|
|
328
|
-
color: #e8eefb;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
.calendar-day-header {
|
|
332
|
-
background: #222;
|
|
333
|
-
color: #eee;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
.calendar-event-allday {
|
|
337
|
-
border-bottom-color: #333;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
.calendar-event-row {
|
|
341
|
-
border-bottom-color: #333;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
.calendar-event-allday {
|
|
345
|
-
background: #2a2510;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
.calendar-event-row--rsvp-accepted {
|
|
349
|
-
--rsvp-bar: #4ade80;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
.calendar-event-row--rsvp-tentative {
|
|
353
|
-
--rsvp-bar: #fbbf24;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
.calendar-event-row--rsvp-needsAction {
|
|
357
|
-
--rsvp-bar: #60a5fa;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
.calendar-event-row--rsvp-declined {
|
|
361
|
-
--rsvp-bar: #f87171;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
.calendar-event-title {
|
|
365
|
-
color: #eee;
|
|
382
|
+
.calendar-pane[data-calendar-theme='system'],
|
|
383
|
+
.calendar-selector[data-calendar-theme='system'] {
|
|
384
|
+
--calendar-scheme: dark;
|
|
385
|
+
--calendar-surface: #1a1a1a;
|
|
386
|
+
--calendar-text: #eee;
|
|
387
|
+
--calendar-border: #333;
|
|
388
|
+
--calendar-prompt: #ccc;
|
|
389
|
+
--calendar-accent: #22d0ef;
|
|
390
|
+
--calendar-hover: #2a2a2a;
|
|
391
|
+
--calendar-chip-text: #eee;
|
|
392
|
+
--calendar-chip-border: #444;
|
|
393
|
+
--calendar-chip-selected: #22d0ef;
|
|
394
|
+
--calendar-chip-selected-text: #081a59;
|
|
395
|
+
--calendar-badge: #081a59;
|
|
396
|
+
--calendar-badge-text: #e8eefb;
|
|
397
|
+
--calendar-header: #222;
|
|
398
|
+
--calendar-allday: #2a2510;
|
|
399
|
+
--calendar-highlight: #10262b;
|
|
400
|
+
--calendar-insert: #999;
|
|
401
|
+
--calendar-insert-hover: #eee;
|
|
402
|
+
--calendar-insert-background: rgba(255, 255, 255, 0.1);
|
|
403
|
+
--calendar-focus: #22d0ef;
|
|
404
|
+
--calendar-rsvp-accepted: #4ade80;
|
|
405
|
+
--calendar-rsvp-tentative: #fbbf24;
|
|
406
|
+
--calendar-rsvp-needsAction: #60a5fa;
|
|
407
|
+
--calendar-rsvp-declined: #f87171;
|
|
408
|
+
--calendar-selector-border: #333;
|
|
409
|
+
--calendar-selector-text: #eee;
|
|
410
|
+
--calendar-selector-shadow: 0 4px 14px rgb(0 0 0 / 50%);
|
|
411
|
+
--calendar-control-border: #444;
|
|
412
|
+
--calendar-control-background: #2a2a2a;
|
|
413
|
+
--calendar-input-background: #2a2a2a;
|
|
414
|
+
--calendar-control-text: #eee;
|
|
415
|
+
--calendar-control-width: 1px;
|
|
416
|
+
--calendar-button-border-style: solid;
|
|
417
|
+
--calendar-input-border-style: solid;
|
|
418
|
+
--calendar-option-hover: #2a2a2a;
|
|
419
|
+
--calendar-option-active: #10262b;
|
|
366
420
|
}
|
|
421
|
+
}
|
|
367
422
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
423
|
+
.calendar-selector {
|
|
424
|
+
position: relative;
|
|
425
|
+
}
|
|
372
426
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
427
|
+
.calendar-selector-trigger {
|
|
428
|
+
display: inline-flex;
|
|
429
|
+
box-sizing: border-box;
|
|
430
|
+
align-items: center;
|
|
431
|
+
justify-content: center;
|
|
432
|
+
vertical-align: top;
|
|
433
|
+
gap: 8px;
|
|
434
|
+
max-width: 100%;
|
|
435
|
+
min-height: 36px;
|
|
436
|
+
padding: 8px 12px;
|
|
437
|
+
border: 1px solid var(--calendar-control-border);
|
|
438
|
+
border-radius: 6px;
|
|
439
|
+
background: var(--calendar-surface);
|
|
440
|
+
color: var(--calendar-text);
|
|
441
|
+
font: inherit;
|
|
442
|
+
cursor: pointer;
|
|
443
|
+
}
|
|
376
444
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
445
|
+
.calendar-selector-trigger:hover {
|
|
446
|
+
background: var(--calendar-hover);
|
|
447
|
+
}
|
|
381
448
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
449
|
+
.calendar-selector-trigger:focus {
|
|
450
|
+
outline: none;
|
|
451
|
+
}
|
|
385
452
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
453
|
+
.calendar-selector-trigger:focus-visible {
|
|
454
|
+
outline: 2px solid var(--calendar-focus);
|
|
455
|
+
outline-offset: 2px;
|
|
456
|
+
}
|
|
390
457
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
458
|
+
.calendar-selector-trigger-label {
|
|
459
|
+
min-width: 0;
|
|
460
|
+
overflow: hidden;
|
|
461
|
+
text-overflow: ellipsis;
|
|
462
|
+
white-space: nowrap;
|
|
395
463
|
}
|
|
396
464
|
|
|
397
|
-
.calendar-selector {
|
|
398
|
-
|
|
465
|
+
.calendar-selector-chevron {
|
|
466
|
+
flex: none;
|
|
467
|
+
width: 12px;
|
|
468
|
+
height: 12px;
|
|
399
469
|
}
|
|
400
470
|
|
|
401
471
|
.calendar-selector-dropdown {
|
|
@@ -405,11 +475,11 @@
|
|
|
405
475
|
min-width: 220px;
|
|
406
476
|
max-width: min(320px, calc(100vw - 32px));
|
|
407
477
|
padding: 8px;
|
|
408
|
-
border: 1px solid
|
|
478
|
+
border: 1px solid var(--calendar-selector-border);
|
|
409
479
|
border-radius: 6px;
|
|
410
|
-
background:
|
|
411
|
-
color:
|
|
412
|
-
box-shadow:
|
|
480
|
+
background: var(--calendar-surface);
|
|
481
|
+
color: var(--calendar-selector-text);
|
|
482
|
+
box-shadow: var(--calendar-selector-shadow);
|
|
413
483
|
}
|
|
414
484
|
|
|
415
485
|
.calendar-selector-dropdown--upward {
|
|
@@ -420,13 +490,22 @@
|
|
|
420
490
|
.calendar-selector-dropdown input[type='search'] {
|
|
421
491
|
box-sizing: border-box;
|
|
422
492
|
width: 100%;
|
|
493
|
+
border: var(--calendar-control-width) var(--calendar-input-border-style) var(--calendar-control-border);
|
|
494
|
+
background: var(--calendar-input-background);
|
|
495
|
+
color: var(--calendar-control-text);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.calendar-selector-dropdown button {
|
|
499
|
+
border: var(--calendar-control-width) var(--calendar-button-border-style) var(--calendar-control-border);
|
|
500
|
+
background: var(--calendar-control-background);
|
|
501
|
+
color: var(--calendar-control-text);
|
|
423
502
|
}
|
|
424
503
|
|
|
425
504
|
.calendar-selector-checkbox {
|
|
426
505
|
flex: none;
|
|
427
506
|
margin: 0;
|
|
428
507
|
pointer-events: none;
|
|
429
|
-
accent-color:
|
|
508
|
+
accent-color: var(--calendar-checkbox);
|
|
430
509
|
}
|
|
431
510
|
|
|
432
511
|
.calendar-selector-actions {
|
|
@@ -450,11 +529,16 @@
|
|
|
450
529
|
padding: 6px;
|
|
451
530
|
border: 0;
|
|
452
531
|
background: transparent;
|
|
532
|
+
color: var(--calendar-control-text);
|
|
453
533
|
text-align: left;
|
|
454
534
|
}
|
|
455
535
|
|
|
536
|
+
.calendar-selector-dropdown [role='option']:hover {
|
|
537
|
+
background: var(--calendar-option-hover);
|
|
538
|
+
}
|
|
539
|
+
|
|
456
540
|
.calendar-selector-dropdown [role='option'][data-active='true'] {
|
|
457
|
-
background:
|
|
541
|
+
background: var(--calendar-option-active);
|
|
458
542
|
}
|
|
459
543
|
|
|
460
544
|
.calendar-selector-dot {
|
|
@@ -471,41 +555,6 @@
|
|
|
471
555
|
white-space: nowrap;
|
|
472
556
|
}
|
|
473
557
|
|
|
474
|
-
@media (prefers-color-scheme: dark) {
|
|
475
|
-
.calendar-selector-dropdown {
|
|
476
|
-
border-color: #333;
|
|
477
|
-
background: #1a1a1a;
|
|
478
|
-
color: #eee;
|
|
479
|
-
box-shadow: 0 4px 14px rgb(0 0 0 / 50%);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
.calendar-selector-dropdown input[type='search'] {
|
|
483
|
-
border: 1px solid #444;
|
|
484
|
-
background: #2a2a2a;
|
|
485
|
-
color: #eee;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
.calendar-selector-dropdown button {
|
|
489
|
-
border: 1px solid #444;
|
|
490
|
-
background: #2a2a2a;
|
|
491
|
-
color: #eee;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
.calendar-selector-dropdown [role='option'] {
|
|
495
|
-
border: 0;
|
|
496
|
-
background: transparent;
|
|
497
|
-
color: #eee;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
.calendar-selector-dropdown [role='option']:hover {
|
|
501
|
-
background: #2a2a2a;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
.calendar-selector-dropdown [role='option'][data-active='true'] {
|
|
505
|
-
background: #10262b;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
558
|
.calendar-event-source-dot {
|
|
510
559
|
display: inline-block;
|
|
511
560
|
width: 8px;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-webapp/calendar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
],
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "tsc && cp src/react/styles.css dist/styles.css",
|
|
27
|
-
"test": "vitest run"
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:theme-browser": "vitest run --config vitest.browser.config.ts"
|
|
28
29
|
},
|
|
29
30
|
"peerDependencies": {
|
|
30
31
|
"react": "^19",
|
|
@@ -37,8 +38,10 @@
|
|
|
37
38
|
"@testing-library/user-event": "^14.6.8",
|
|
38
39
|
"@types/react": "^19.0.0",
|
|
39
40
|
"@types/react-dom": "^19.0.0",
|
|
41
|
+
"@vitest/browser": "2.1.9",
|
|
40
42
|
"fake-indexeddb": "^6.0.0",
|
|
41
43
|
"jsdom": "^30.0.1",
|
|
44
|
+
"playwright": "1.51.1",
|
|
42
45
|
"react": "^19.0.0",
|
|
43
46
|
"react-dom": "^19.0.0",
|
|
44
47
|
"typescript": "^5.6.0",
|