@open-webapp/calendar 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -180,6 +180,8 @@ Built-in copy (overridable only through the slots above):
180
180
 
181
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.
182
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
+
183
185
  ### CalendarSelector props
184
186
 
185
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".
@@ -201,7 +203,7 @@ import '@open-webapp/calendar/styles.css';
201
203
 
202
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.
203
205
 
204
- Each pane forwards its requested mode to its unified grouped footer selector, including both account groups, account-local actions and the shared search. The existing single-trigger layout and cross-account keyboard navigation are preserved. 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.
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.
205
207
 
206
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.
207
209
 
@@ -209,6 +211,8 @@ The exported standalone selector keeps its single-list props and behavior; Selec
209
211
 
210
212
  ### Host CSS
211
213
 
214
+ Day ribbon styling: override `--calendar-today-accent` to recolour today's ribbon; `--calendar-day-header-height` tunes the sticky label offset.
215
+
212
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:
213
217
 
214
218
  - `--calendar-footer-height` (default `64px`, set on `.calendar-pane`; override to match host footer)
@@ -217,6 +221,19 @@ The exported standalone selector keeps its single-list props and behavior; Selec
217
221
 
218
222
  The Unaccepted count badge uses `.calendar-rsvp-chip-badge`, with light and dark styling included in `styles.css`.
219
223
 
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
+
226
+ ## 0.6.0 (minor, additive-visual)
227
+
228
+ - Each day has a thin vertical ribbon left of its events with a rotated `Mmm D` label; today's ribbon uses the new `--calendar-today-accent` token (defaults to the selected-chip colour in light and dark). The sticky day header is unchanged.
229
+ - The RSVP status bar moved from the left edge to the right edge of event rows.
230
+ - New helper `formatRibbonLabel`.
231
+
232
+ ## 0.5.1 (patch)
233
+
234
+ - Corrects footer and standalone selector affordance with a padded, rounded 36px-minimum trigger, decorative chevron, themed hover and keyboard-focus treatment, and long-label containment.
235
+ - 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.
236
+
220
237
  ## 0.5.0 (minor, additive)
221
238
 
222
239
  - Adds optional `theme?: 'system' | 'light' | 'dark'` to both React components, with live OS tracking by default and live per-instance overrides.
@@ -52,6 +52,8 @@ export interface DayGroup {
52
52
  * to), inserted in correct sorted position.
53
53
  */
54
54
  export declare function groupByDay(events: CalendarEvent[], todayIso?: string): DayGroup[];
55
+ /** Short "Mmm D" label for the day ribbon: no year, no Today/Tomorrow. */
56
+ export declare function formatRibbonLabel(dateKey: string): string;
55
57
  /**
56
58
  * "Today · Sept 26" / "Tomorrow · Sept 27" / else "Sept 28"-style.
57
59
  * Includes year only when the event's year differs from today's year.
@@ -168,6 +168,10 @@ function formatMonthDay(dateKey, includeYear) {
168
168
  const base = `${monthName} ${day}`;
169
169
  return includeYear ? `${base}, ${year}` : base;
170
170
  }
171
+ /** Short "Mmm D" label for the day ribbon: no year, no Today/Tomorrow. */
172
+ export function formatRibbonLabel(dateKey) {
173
+ return formatMonthDay(dateKey, false);
174
+ }
171
175
  function addDaysToIso(dateKey, days) {
172
176
  const { year, month, day } = parseIsoDateParts(dateKey);
173
177
  const d = new Date(Date.UTC(year, month - 1, day));
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef, useState } from 'react';
3
- import { filterByRsvp, groupByDay, getOrganizerLabel, formatDayHeader, isEventCurrent, findNextUpcomingEvent, getCalendarEventKey, DEFAULT_RSVP_STATUSES, } from '../core/events.js';
3
+ import { filterByRsvp, groupByDay, getOrganizerLabel, formatDayHeader, formatRibbonLabel, isEventCurrent, findNextUpcomingEvent, getCalendarEventKey, DEFAULT_RSVP_STATUSES, } from '../core/events.js';
4
4
  import { GroupedCalendarSelector } from './CalendarSelector.js';
5
5
  function toggleRsvpStatus(selection, status) {
6
6
  const next = new Set(selection);
@@ -305,45 +305,45 @@ export function CalendarPane(props) {
305
305
  else {
306
306
  dayHeaderRefs.current.delete(group.dateKey);
307
307
  }
308
- }, children: formatDayHeader(group.dateKey, todayIso) }), group.events.map((event) => {
309
- const eventKey = getCalendarEventKey(event);
310
- const calendar = calendarsByKey.get(`${event.sourceId}\u0000${event.calendarId}`);
311
- const showCalendarLabel = calendar && !calendar.primary;
312
- const organizerLabel = getOrganizerLabel(event);
313
- const showOrganizerLabel = multi
314
- ? true
315
- : organizerLabel && (!showCalendarLabel || organizerLabel !== calendar.summary);
316
- const organizerText = multi
317
- ? organizerLabel
318
- ? `${organizerLabel} · ${sourceEmail(event.sourceId)}`
319
- : sourceEmail(event.sourceId)
320
- : organizerLabel;
321
- const rsvpStatus = RSVP_CHIPS.some((chip) => chip.status === event.selfResponseStatus)
322
- ? event.selfResponseStatus
323
- : 'needsAction';
324
- if (event.isAllDay) {
325
- return (_jsxs("div", { className: `calendar-event-allday calendar-event-row--rsvp-${rsvpStatus}`, children: [_jsxs("span", { className: "calendar-event-title-wrap", children: [_jsx("span", { className: "calendar-event-title", children: event.summary }), props.renderEventAction?.(event)] }), showCalendarLabel && (_jsxs("span", { className: "calendar-event-calendar", children: [_jsx("span", { className: "calendar-event-calendar-dot", style: { backgroundColor: calendar.backgroundColor } }), calendar.summary] })), showOrganizerLabel && (_jsxs("span", { className: "calendar-event-organizer", children: [multi && (_jsx("span", { className: "calendar-event-source-dot", style: { backgroundColor: sourceColor(event.sourceId) } })), organizerText] }))] }, eventKey));
326
- }
327
- const hasLink = usableUrl(event.htmlLink) !== null;
328
- return (_jsxs("div", { className: `calendar-event-row${currentEventKeys.has(eventKey) ? ' calendar-event-row--current' : ''}${selectedEventKey === eventKey ? ' calendar-event-row--selected' : ''} calendar-event-row--rsvp-${rsvpStatus}`, ref: (el) => {
329
- if (el) {
330
- timedEventRefs.current.set(eventKey, el);
331
- }
332
- else {
333
- timedEventRefs.current.delete(eventKey);
334
- }
335
- }, children: [_jsxs("div", { className: "calendar-event-header", children: [_jsx("span", { className: "calendar-event-time", children: formatTimeRange(event) }), event.joinUrl && (_jsx("a", { className: "calendar-event-join", href: event.joinUrl, target: "_blank", rel: "noopener noreferrer", onClick: (e) => e.stopPropagation(), children: "Join" }))] }), _jsxs("div", { className: "calendar-event-title-wrap", children: [hasLink ? (_jsx("a", { className: "calendar-event-title-link", href: event.htmlLink, target: "_blank", rel: "noopener noreferrer", onClick: (e) => {
336
- if (isDesktop &&
337
- e.button === 0 &&
338
- !e.ctrlKey &&
339
- !e.metaKey &&
340
- !e.shiftKey &&
341
- !e.altKey) {
342
- e.preventDefault();
343
- openEventPopup(event.htmlLink, popupRef, popupName);
344
- }
345
- }, children: _jsx("span", { className: "calendar-event-title", children: event.summary }) })) : (_jsx("span", { className: "calendar-event-title", children: event.summary })), props.renderEventAction?.(event)] }), showCalendarLabel && (_jsxs("span", { className: "calendar-event-calendar", children: [_jsx("span", { className: "calendar-event-calendar-dot", style: { backgroundColor: calendar.backgroundColor } }), calendar.summary] })), showOrganizerLabel && (_jsxs("span", { className: "calendar-event-organizer", children: [multi && (_jsx("span", { className: "calendar-event-source-dot", style: { backgroundColor: sourceColor(event.sourceId) } })), organizerText] }))] }, eventKey));
346
- })] }, group.dateKey))), loading && _jsx("p", { className: "calendar-empty-state-text", children: "Loading calendar\u2026" }), filteredEvents.length === 0 && !loading && !error && needsScopeSources.length === 0 && _jsx("p", { className: "calendar-empty-state-text", children: emptyStateText })] }))] }), showCalendarSelector && (_jsx("footer", { className: "calendar-pane-footer", children: _jsx(GroupedCalendarSelector, { theme: theme, groups: calendarSources.map((s) => ({
308
+ }, children: formatDayHeader(group.dateKey, todayIso) }), group.events.length > 0 && (_jsxs("div", { className: "calendar-day-body", children: [_jsx("div", { className: "calendar-day-ribbon", "aria-hidden": "true", "data-today": group.dateKey === todayIso ? 'true' : undefined, children: _jsx("span", { className: "calendar-day-ribbon-label", children: formatRibbonLabel(group.dateKey) }) }), _jsx("div", { className: "calendar-day-events", children: group.events.map((event) => {
309
+ const eventKey = getCalendarEventKey(event);
310
+ const calendar = calendarsByKey.get(`${event.sourceId}\u0000${event.calendarId}`);
311
+ const showCalendarLabel = calendar && !calendar.primary;
312
+ const organizerLabel = getOrganizerLabel(event);
313
+ const showOrganizerLabel = multi
314
+ ? true
315
+ : organizerLabel && (!showCalendarLabel || organizerLabel !== calendar.summary);
316
+ const organizerText = multi
317
+ ? organizerLabel
318
+ ? `${organizerLabel} · ${sourceEmail(event.sourceId)}`
319
+ : sourceEmail(event.sourceId)
320
+ : organizerLabel;
321
+ const rsvpStatus = RSVP_CHIPS.some((chip) => chip.status === event.selfResponseStatus)
322
+ ? event.selfResponseStatus
323
+ : 'needsAction';
324
+ if (event.isAllDay) {
325
+ return (_jsxs("div", { className: `calendar-event-allday calendar-event-row--rsvp-${rsvpStatus}`, children: [_jsxs("span", { className: "calendar-event-title-wrap", children: [_jsx("span", { className: "calendar-event-title", children: event.summary }), props.renderEventAction?.(event)] }), showCalendarLabel && (_jsxs("span", { className: "calendar-event-calendar", children: [_jsx("span", { className: "calendar-event-calendar-dot", style: { backgroundColor: calendar.backgroundColor } }), calendar.summary] })), showOrganizerLabel && (_jsxs("span", { className: "calendar-event-organizer", children: [multi && (_jsx("span", { className: "calendar-event-source-dot", style: { backgroundColor: sourceColor(event.sourceId) } })), organizerText] }))] }, eventKey));
326
+ }
327
+ const hasLink = usableUrl(event.htmlLink) !== null;
328
+ return (_jsxs("div", { className: `calendar-event-row${currentEventKeys.has(eventKey) ? ' calendar-event-row--current' : ''}${selectedEventKey === eventKey ? ' calendar-event-row--selected' : ''} calendar-event-row--rsvp-${rsvpStatus}`, ref: (el) => {
329
+ if (el) {
330
+ timedEventRefs.current.set(eventKey, el);
331
+ }
332
+ else {
333
+ timedEventRefs.current.delete(eventKey);
334
+ }
335
+ }, children: [_jsxs("div", { className: "calendar-event-header", children: [_jsx("span", { className: "calendar-event-time", children: formatTimeRange(event) }), event.joinUrl && (_jsx("a", { className: "calendar-event-join", href: event.joinUrl, target: "_blank", rel: "noopener noreferrer", onClick: (e) => e.stopPropagation(), children: "Join" }))] }), _jsxs("div", { className: "calendar-event-title-wrap", children: [hasLink ? (_jsx("a", { className: "calendar-event-title-link", href: event.htmlLink, target: "_blank", rel: "noopener noreferrer", onClick: (e) => {
336
+ if (isDesktop &&
337
+ e.button === 0 &&
338
+ !e.ctrlKey &&
339
+ !e.metaKey &&
340
+ !e.shiftKey &&
341
+ !e.altKey) {
342
+ e.preventDefault();
343
+ openEventPopup(event.htmlLink, popupRef, popupName);
344
+ }
345
+ }, children: _jsx("span", { className: "calendar-event-title", children: event.summary }) })) : (_jsx("span", { className: "calendar-event-title", children: event.summary })), props.renderEventAction?.(event)] }), showCalendarLabel && (_jsxs("span", { className: "calendar-event-calendar", children: [_jsx("span", { className: "calendar-event-calendar-dot", style: { backgroundColor: calendar.backgroundColor } }), calendar.summary] })), showOrganizerLabel && (_jsxs("span", { className: "calendar-event-organizer", children: [multi && (_jsx("span", { className: "calendar-event-source-dot", style: { backgroundColor: sourceColor(event.sourceId) } })), organizerText] }))] }, eventKey));
346
+ }) })] }))] }, group.dateKey))), loading && _jsx("p", { className: "calendar-empty-state-text", children: "Loading calendar\u2026" }), filteredEvents.length === 0 && !loading && !error && needsScopeSources.length === 0 && _jsx("p", { className: "calendar-empty-state-text", children: emptyStateText })] }))] }), showCalendarSelector && (_jsx("footer", { className: "calendar-pane-footer", children: _jsx(GroupedCalendarSelector, { theme: theme, groups: calendarSources.map((s) => ({
347
347
  id: s.id,
348
348
  calendars: controller.getCalendars(s.id),
349
349
  selectedIds: controller.getSelectedIds(s.id),
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
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
4
  export function CalendarSelector({ calendars, selectedIds, onChange, heading, theme = 'system' }) {
@@ -90,11 +90,11 @@ export function GroupedCalendarSelector({ groups, theme = 'system' }) {
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", "data-calendar-theme": theme, ref: rootRef, children: [_jsx("button", { ref: buttonRef, type: "button", role: "combobox", "aria-label": label, "aria-expanded": open, "aria-controls": listboxId, "aria-haspopup": "listbox", onClick: () => {
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
@@ -11,6 +11,7 @@
11
11
  --calendar-chip-text: #333;
12
12
  --calendar-chip-border: #ccc;
13
13
  --calendar-chip-selected: #081a59;
14
+ --calendar-today-accent: #081a59;
14
15
  --calendar-chip-selected-text: #ffffff;
15
16
  --calendar-badge: #e8eefb;
16
17
  --calendar-badge-text: #081a59;
@@ -193,12 +194,49 @@
193
194
  z-index: 1;
194
195
  }
195
196
 
197
+ .calendar-day-body {
198
+ display: flex;
199
+ }
200
+
201
+ .calendar-day-events {
202
+ flex: 1;
203
+ min-width: 0;
204
+ }
205
+
206
+ .calendar-day-ribbon {
207
+ flex: 0 0 16px;
208
+ width: 16px;
209
+ min-height: 48px;
210
+ background: var(--calendar-header);
211
+ color: var(--calendar-chip-text);
212
+ user-select: none;
213
+ font-size: 11px;
214
+ font-weight: 600;
215
+ display: flex;
216
+ justify-content: center;
217
+ align-items: flex-start;
218
+ }
219
+
220
+ .calendar-day-ribbon[data-today='true'] {
221
+ background: var(--calendar-today-accent);
222
+ color: var(--calendar-chip-selected-text);
223
+ }
224
+
225
+ .calendar-day-ribbon-label {
226
+ writing-mode: vertical-rl;
227
+ transform: rotate(180deg);
228
+ position: sticky;
229
+ top: calc(var(--chip-row-height, 44px) + var(--calendar-day-header-height, 33px));
230
+ white-space: nowrap;
231
+ padding: 6px 0;
232
+ }
233
+
196
234
  .calendar-event-allday {
197
235
  display: flex;
198
236
  align-items: center;
199
237
  gap: 8px;
200
238
  padding: 10px 16px;
201
- border-left: 3px solid var(--rsvp-bar, transparent);
239
+ border-right: 3px solid var(--rsvp-bar, transparent);
202
240
  border-bottom: 1px solid var(--calendar-border);
203
241
  }
204
242
 
@@ -207,7 +245,7 @@
207
245
  flex-direction: column;
208
246
  gap: 8px;
209
247
  padding: 10px 16px;
210
- border-left: 3px solid var(--rsvp-bar, transparent);
248
+ border-right: 3px solid var(--rsvp-bar, transparent);
211
249
  border-bottom: 1px solid var(--calendar-border);
212
250
  }
213
251
 
@@ -350,6 +388,7 @@
350
388
  --calendar-chip-text: #eee;
351
389
  --calendar-chip-border: #444;
352
390
  --calendar-chip-selected: #22d0ef;
391
+ --calendar-today-accent: #22d0ef;
353
392
  --calendar-chip-selected-text: #081a59;
354
393
  --calendar-badge: #081a59;
355
394
  --calendar-badge-text: #e8eefb;
@@ -391,6 +430,7 @@
391
430
  --calendar-chip-text: #eee;
392
431
  --calendar-chip-border: #444;
393
432
  --calendar-chip-selected: #22d0ef;
433
+ --calendar-today-accent: #22d0ef;
394
434
  --calendar-chip-selected-text: #081a59;
395
435
  --calendar-badge: #081a59;
396
436
  --calendar-badge-text: #e8eefb;
@@ -424,10 +464,48 @@
424
464
  position: relative;
425
465
  }
426
466
 
427
- .calendar-selector > [role='combobox'] {
428
- color: var(--calendar-control-text);
429
- background: var(--calendar-control-background);
430
- border-color: var(--calendar-control-border);
467
+ .calendar-selector-trigger {
468
+ display: inline-flex;
469
+ box-sizing: border-box;
470
+ align-items: center;
471
+ justify-content: center;
472
+ vertical-align: top;
473
+ gap: 8px;
474
+ max-width: 100%;
475
+ min-height: 36px;
476
+ padding: 8px 12px;
477
+ border: 1px solid var(--calendar-control-border);
478
+ border-radius: 6px;
479
+ background: var(--calendar-surface);
480
+ color: var(--calendar-text);
481
+ font: inherit;
482
+ cursor: pointer;
483
+ }
484
+
485
+ .calendar-selector-trigger:hover {
486
+ background: var(--calendar-hover);
487
+ }
488
+
489
+ .calendar-selector-trigger:focus {
490
+ outline: none;
491
+ }
492
+
493
+ .calendar-selector-trigger:focus-visible {
494
+ outline: 2px solid var(--calendar-focus);
495
+ outline-offset: 2px;
496
+ }
497
+
498
+ .calendar-selector-trigger-label {
499
+ min-width: 0;
500
+ overflow: hidden;
501
+ text-overflow: ellipsis;
502
+ white-space: nowrap;
503
+ }
504
+
505
+ .calendar-selector-chevron {
506
+ flex: none;
507
+ width: 12px;
508
+ height: 12px;
431
509
  }
432
510
 
433
511
  .calendar-selector-dropdown {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-webapp/calendar",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",